Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Initial socket support #30

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6f8cf01
WIP: basic socket support
milseman Feb 26, 2021
6e4b421
WIP: shutdown
milseman Feb 26, 2021
d9a1323
WIP: listen
milseman Feb 27, 2021
22f9f41
WIP: add FileDescriptor forwarding methods
milseman Feb 27, 2021
9b4700f
WIP: send/recv
milseman Feb 27, 2021
9014393
WIP: socket options
milseman Feb 27, 2021
aae1ff5
WIP: Socket get/set options
milseman Feb 27, 2021
3d532f3
Add nice wrappers for sockaddr and sockaddr_in (#1)
milseman Apr 10, 2021
241fbbd
WIP: accept, connect, bind
milseman Feb 28, 2021
4f6b42f
Update Sources/System/Sockets/SocketOperations.swift
milseman Mar 1, 2021
a962528
Update Sources/System/Sockets/SocketOperations.swift
milseman Mar 1, 2021
bca51a4
Implement local addresses, address families, sendmsg/recvmsg (#2)
lorentey Mar 1, 2021
661207f
WIP: Apply doc comments and make concrete socket addresses optional p…
milseman Mar 1, 2021
763febd
WIP: fixup the samples and rename things to make me less nervous
milseman Mar 1, 2021
5eec031
WIP: some name changes
milseman Mar 1, 2021
898e0ef
WIP: add some AEIC annotations
milseman Mar 1, 2021
84846f7
Implement sendto/recvfrom; other minor changes
lorentey Mar 1, 2021
008dc40
Doc updates
lorentey Mar 2, 2021
ef5f7f5
Implement getnameinfo.
lorentey Mar 2, 2021
0fdf652
WIP: docs and cleanup
milseman Mar 2, 2021
4afc2de
Network receptacles (#3)
lorentey Mar 3, 2021
051731d
Socket updates (#4)
lorentey Mar 4, 2021
e7f6310
WIP: Remove uncheckedSocket for now
milseman Mar 5, 2021
2b0b28c
Rebase and update to top-of-main
milseman Apr 10, 2021
485efdd
WIP: First attempt at extracting into separate module
milseman Apr 10, 2021
4e38f69
WIP: System sockets as separate module working
milseman Apr 10, 2021
ce12fda
Separate test target
milseman Apr 10, 2021
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
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": {
"pins": [
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": {
"branch": null,
"revision": "9564d61b08a5335ae0a36f789a7d71493eacadfc",
"version": "0.3.2"
}
}
]
},
"version": 1
}
38 changes: 33 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

import PackageDescription

let settings: [SwiftSetting]? = [
.define("SYSTEM_PACKAGE")
]

let targets: [PackageDescription.Target] = [
.target(
name: "SystemPackage",
Expand All @@ -24,19 +28,43 @@ let targets: [PackageDescription.Target] = [
.target(
name: "CSystem",
dependencies: []),

.target(
name: "SystemSockets",
dependencies: ["SystemPackage"]),

.testTarget(
name: "SystemTests",
dependencies: ["SystemPackage"],
swiftSettings: [
.define("SYSTEM_PACKAGE")
]),
swiftSettings: settings
),

.testTarget(
name: "SystemSocketsTests",
dependencies: ["SystemSockets"],
swiftSettings: settings
),

.target(
name: "Samples",
dependencies: [
"SystemPackage",
"SystemSockets",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
path: "Sources/Samples",
swiftSettings: settings
),
]

let package = Package(
name: "swift-system",
products: [
.library(name: "SystemPackage", targets: ["SystemPackage"]),
.library(name: "SystemPackage", targets: ["SystemPackage"]),
.executable(name: "system-samples", targets: ["Samples"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.0"),
],
dependencies: [],
targets: targets
)
113 changes: 113 additions & 0 deletions Sources/Samples/Connect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2021 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
#if SYSTEM_PACKAGE
import SystemPackage
import SystemSockets
#else
import System
#error("No socket support")
#endif

struct Connect: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "connect",
abstract: "Open a connection and send lines from stdin over it."
)

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

@Argument(help: "The port number (or service name) to connect to.")
var service: String

@Flag(help: "Use IPv4")
var ipv4: Bool = false

@Flag(help: "Use IPv6")
var ipv6: Bool = false

@Flag(help: "Use UDP")
var udp: Bool = false

@Flag(help: "Send data out-of-band")
var outOfBand: Bool = false

@Option(help: "TCP connection timeout, in seconds")
var connectionTimeout: CInt?

@Option(help: "Socket send timeout, in seconds")
var sendTimeout: CInt?

@Option(help: "Socket receive timeout, in seconds")
var receiveTimeout: CInt?

@Option(help: "TCP connection keepalive interval, in seconds")
var keepalive: CInt?

func connect(
to addresses: [SocketAddress.Info]
) throws -> (SocketDescriptor, SocketAddress)? {
// Only try the first address for now
guard let addressinfo = addresses.first else { return nil }
print(addressinfo)
let socket = try SocketDescriptor.open(
addressinfo.domain,
addressinfo.type,
addressinfo.protocol)
do {
if let connectionTimeout = connectionTimeout {
try socket.setOption(.tcp, .tcpConnectionTimeout, to: connectionTimeout)
}
if let sendTimeout = sendTimeout {
try socket.setOption(.socketOption, .sendTimeout, to: sendTimeout)
}
if let receiveTimeout = receiveTimeout {
try socket.setOption(.socketOption, .receiveTimeout, to: receiveTimeout)
}
if let keepalive = keepalive {
try socket.setOption(.tcp, .tcpKeepAlive, to: keepalive)
}
try socket.connect(to: addressinfo.address)
return (socket, addressinfo.address)
}
catch {
try? socket.close()
throw error
}
}

func run() throws {
let addresses = try SocketAddress.resolveName(
hostname: hostname,
service: service,
family: ipv6 ? .ipv6 : .ipv4,
type: udp ? .datagram : .stream)

guard let (socket, address) = try connect(to: addresses) else {
complain("Can't connect to \(hostname)")
throw ExitCode.failure
}
complain("Connected to \(address.niceDescription)")

let flags: SocketDescriptor.MessageFlags = outOfBand ? .outOfBand : .none
try socket.closeAfter {
while var line = readLine(strippingNewline: false) {
try line.withUTF8 { buffer in
var buffer = UnsafeRawBufferPointer(buffer)
while !buffer.isEmpty {
let c = try socket.send(buffer, flags: flags)
buffer = .init(rebasing: buffer[c...])
}
}
}
}
}
}
124 changes: 124 additions & 0 deletions Sources/Samples/Listen.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2021 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
#if SYSTEM_PACKAGE
import SystemPackage
import SystemSockets
#else
import System
#error("No socket support")
#endif

struct Listen: ParsableCommand {
static var configuration = CommandConfiguration(
commandName: "listen",
abstract: "Listen for an incoming connection and print received text to stdout."
)

@Argument(help: "The port number (or service name) to listen on.")
var service: String

@Flag(help: "Use IPv4")
var ipv4: Bool = false

@Flag(help: "Use IPv6")
var ipv6: Bool = false

@Flag(help: "Use UDP")
var udp: Bool = false

func startServer(
on addresses: [SocketAddress.Info]
) throws -> (SocketDescriptor, SocketAddress.Info)? {
for info in addresses {
do {
let socket = try SocketDescriptor.open(
info.domain,
info.type,
info.protocol)
do {
try socket.bind(to: info.address)
if !info.type.isConnectionless {
try socket.listen(backlog: 10)
}
return (socket, info)
}
catch {
try? socket.close()
throw error
}
}
catch {
continue
}
}
return nil
}

func prefix(
client: SocketAddress,
flags: SocketDescriptor.MessageFlags
) -> String {
var prefix: [String] = []
if client.family != .unspecified {
prefix.append("client: \(client.niceDescription)")
}
if flags != .none {
prefix.append("flags: \(flags)")
}
guard !prefix.isEmpty else { return "" }
return "<\(prefix.joined(separator: ", "))> "
}

func run() throws {
let addresses = try SocketAddress.resolveName(
hostname: nil,
service: service,
flags: .canonicalName,
family: ipv6 ? .ipv6 : .ipv4,
type: udp ? .datagram : .stream)


guard let (socket, address) = try startServer(on: addresses) else {
complain("Can't listen on \(service)")
throw ExitCode.failure
}
complain("Listening on \(address.address.niceDescription)")

var client = SocketAddress()
let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 1)
defer { buffer.deallocate() }

var ancillary = SocketDescriptor.AncillaryMessageBuffer()
try socket.closeAfter {
if udp {
while true {
let (count, flags) =
try socket.receive(into: buffer, sender: &client, ancillary: &ancillary)
print(prefix(client: client, flags: flags), terminator: "")
try FileDescriptor.standardOutput.writeAll(buffer[..<count])
}
} else {
let conn = try socket.accept(client: &client)
complain("Connection from \(client.niceDescription)")
try conn.closeAfter {
while true {
let (count, flags) =
try conn.receive(into: buffer, sender: &client, ancillary: &ancillary)
guard count > 0 else { break }
print(prefix(client: client, flags: flags), terminator: "")
try FileDescriptor.standardOutput.writeAll(buffer[..<count])
}
}
}
}
}
}

Loading