Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ xcuserdata/
.*.sw?
/.swiftpm
.docc-build
Package.resolved
26 changes: 25 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,12 @@ let package = Package(
platforms: platforms,
products: [
.library(name: "SystemPackage", targets: ["SystemPackage"]),
.library(name: "SystemSockets", targets: ["SystemSockets"]),
.executable(name: "system-samples", targets: ["Samples"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
],
dependencies: [],
targets: [
.target(
name: "CSystem",
Expand All @@ -144,6 +148,26 @@ let package = Package(
name: "MemberImportVisibility",
dependencies: ["SystemPackage"],
swiftSettings: [.enableUpcomingFeature("MemberImportVisibility")]),
.target(
name: "SystemSockets",
dependencies: ["SystemPackage", "CSystem"],
path: "Sources/SystemSockets",
cSettings: cSettings,
swiftSettings: swiftSettings),
.testTarget(
name: "SystemSocketsTests",
dependencies: ["SystemSockets"],
cSettings: cSettings,
swiftSettings: swiftSettings),
.executableTarget(
name: "Samples",
dependencies: [
"SystemPackage",
"SystemSockets",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
path: "Sources/Samples",
swiftSettings: swiftSettings),
],
swiftLanguageModes: [.v6]
)
71 changes: 71 additions & 0 deletions Sources/Samples/Connect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2021 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

import ArgumentParser
import SystemSockets

struct Connect: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Connect to a TCP server and send/receive messages"
)

@Argument(help: "The host to connect to")
var host: String

@Argument(help: "The port to connect to")
var port: UInt16

@Option(name: .shortAndLong, help: "Message to send")
var message: String = "Hello from swift-system sockets!"

func run() throws {
print("Resolving \(host)...")

// Resolve the hostname
let hints = SocketAddress.ResolutionHints(family: .ipv4, socketType: .stream, protocol: .tcp)
let addresses = try SocketAddress.resolve(hostname: host, service: "\(port)", hints: hints)

guard let first = addresses.first else {
print("No addresses found for \(host)")
return
}

print("Connecting to \(first.address.ipv4?.description ?? "unknown")...")

// Create socket
let socket = try SocketDescriptor.open(first.family, first.socketType, protocol: first.protocol)
defer { try? socket.close() }

// Connect
try socket.connect(to: first.address)
print("Connected!")

// Send message
let messageBytes = Array(message.utf8)
let sent = try messageBytes.withUnsafeBytes { buffer in
try socket.send(UnsafeRawBufferPointer(buffer))
}
print("Sent \(sent) bytes: \(message)")

// Receive response
var buffer = [UInt8](repeating: 0, count: 4096)
let received = try buffer.withUnsafeMutableBytes { buffer in
try socket.receive(into: buffer)
}

if received > 0 {
let response = String(decoding: buffer.prefix(received), as: UTF8.self)
print("Received \(received) bytes: \(response)")
} else {
print("Connection closed by server")
}

print("Done!")
}
}
80 changes: 80 additions & 0 deletions Sources/Samples/Listen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2021 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

import ArgumentParser
import SystemSockets

struct Listen: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Run a simple TCP echo server"
)

@Option(name: .shortAndLong, help: "The port to listen on")
var port: UInt16 = 8080

@Option(name: .shortAndLong, help: "Maximum number of connections to accept (0 for unlimited)")
var maxConnections: Int = 0

func run() throws {
// Create socket
let server = try SocketDescriptor.open(.ipv4, .stream, protocol: .tcp)
defer { try? server.close() }

// Set reuse address
try server.setReuseAddress(true)

// Bind
let address = SocketAddress(ipv4: IPv4Address.any(port: port))
try server.bind(to: address)

// Listen
try server.listen(backlog: 5)

var localAddr = SocketAddress()
try server.getLocalAddress(into: &localAddr)
print("Listening on \(localAddr.ipv4?.description ?? "unknown")...")

var connectionCount = 0
while maxConnections == 0 || connectionCount < maxConnections {
print("Waiting for connection...")

var peerAddr = SocketAddress()
let client = try server.accept(client: &peerAddr)
connectionCount += 1

print("[\(connectionCount)] Connection from \(peerAddr.ipv4?.description ?? "unknown")")

// Handle client in a simple blocking manner
defer { try? client.close() }

var buffer = [UInt8](repeating: 0, count: 4096)

while true {
let received = try buffer.withUnsafeMutableBytes { buffer in
try client.receive(into: buffer)
}
if received == 0 {
print("[\(connectionCount)] Client disconnected")
break
}

let message = String(decoding: buffer.prefix(received), as: UTF8.self)
print("[\(connectionCount)] Received \(received) bytes: \(message)")

// Echo back
let sent = try buffer.prefix(received).withUnsafeBytes { buffer in
try client.send(UnsafeRawBufferPointer(buffer))
}
print("[\(connectionCount)] Echoed \(sent) bytes")
}
}

print("Server shutting down after \(connectionCount) connections")
}
}
102 changes: 102 additions & 0 deletions Sources/Samples/Resolve.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2021 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

import ArgumentParser
import SystemSockets

struct Resolve: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Resolve a hostname to IP addresses using getaddrinfo"
)

@Argument(help: "The hostname to resolve")
var hostname: String

@Option(name: .shortAndLong, help: "Service name or port number")
var service: String?

@Flag(name: .long, help: "Show only IPv4 addresses")
var ipv4Only: Bool = false

@Flag(name: .long, help: "Show only IPv6 addresses")
var ipv6Only: Bool = false

func run() throws {
let family: SocketDescriptor.Domain?
if ipv4Only {
family = .ipv4
} else if ipv6Only {
family = .ipv6
} else {
family = nil
}

let hints = SocketAddress.ResolutionHints(family: family)

print("Resolving \(hostname)...")

let addresses = try SocketAddress.resolve(
hostname: hostname,
service: service,
hints: hints
)

if addresses.isEmpty {
print("No addresses found")
return
}

print("Found \(addresses.count) address(es):\n")

for (index, info) in addresses.enumerated() {
print("[\(index + 1)] Family: \(familyName(info.family))")
print(" Type: \(typeName(info.socketType))")
print(" Protocol: \(protocolName(info.protocol))")

if let ipv4 = info.address.ipv4 {
print(" Address: \(ipv4.addressString):\(ipv4.port)")
} else if let ipv6 = info.address.ipv6 {
print(" Address: [\(ipv6.addressString)]:\(ipv6.port)")
}

if let canonName = info.canonicalName {
print(" Canonical: \(canonName)")
}

print()
}
}

private func familyName(_ domain: SocketDescriptor.Domain) -> String {
switch domain {
case .ipv4: return "IPv4"
case .ipv6: return "IPv6"
case .local: return "Unix"
default: return "Unknown (\(domain.rawValue))"
}
}

private func typeName(_ type: SocketDescriptor.ConnectionType) -> String {
switch type {
case .stream: return "Stream (TCP)"
case .datagram: return "Datagram (UDP)"
case .raw: return "Raw"
default: return "Unknown (\(type.rawValue))"
}
}

private func protocolName(_ proto: SocketDescriptor.ProtocolID) -> String {
switch proto {
case .tcp: return "TCP"
case .udp: return "UDP"
case .ip: return "IP"
default: return "Unknown (\(proto.rawValue))"
}
}
}
84 changes: 84 additions & 0 deletions Sources/Samples/ReverseResolve.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2021 - 2025 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

import ArgumentParser
import SystemSockets

struct ReverseResolve: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Reverse resolve an IP address to a hostname using getnameinfo"
)

@Argument(help: "The IP address to resolve (IPv4 or IPv6)")
var address: String

@Option(name: .shortAndLong, help: "Port number")
var port: UInt16 = 0

@Flag(name: .long, help: "Request numeric host (don't resolve)")
var numericHost: Bool = false

@Flag(name: .long, help: "Request numeric service (don't resolve)")
var numericService: Bool = false

func run() throws {
// Try to parse as IPv4
if let ipv4 = IPv4Address(address, port: port) {
let sockAddr = SocketAddress(ipv4: ipv4)
try resolve(sockAddr)
return
}

// Try to parse as IPv6
if let ipv6 = IPv6Address(address, port: port) {
let sockAddr = SocketAddress(ipv6: ipv6)
try resolve(sockAddr)
return
}

print("Error: '\(address)' is not a valid IPv4 or IPv6 address")
}

private func resolve(_ address: SocketAddress) throws {
var flags: SocketAddress.ReverseResolutionFlags = []
if numericHost {
flags.insert(.numericHost)
}
if numericService {
flags.insert(.numericService)
}

print("Resolving \(addressDescription(address))...")

let info = try address.reverseLookup(flags: flags)

print()
if let host = info.hostname {
print("Host: \(host)")
} else {
print("Host: (not resolved)")
}

if let service = info.service {
print("Service: \(service)")
} else {
print("Service: (not resolved)")
}
}

private func addressDescription(_ address: SocketAddress) -> String {
if let ipv4 = address.ipv4 {
return "\(ipv4.addressString):\(ipv4.port)"
} else if let ipv6 = address.ipv6 {
return "[\(ipv6.addressString)]:\(ipv6.port)"
} else {
return "(unknown address)"
}
}
}
Loading
Loading