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

Add support for IPv4 to TCPSocket #40

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
3 changes: 1 addition & 2 deletions Sources/DefaultHTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ public final class DefaultHTTPServer: HTTPServer {
return
}
logger.info("Starting HTTP server on [\(interface)]:\(port) ...")
acceptSocket = try TCPSocket()
try acceptSocket.bind(port: port, interface: interface)
acceptSocket = try TCPSocket(boundToPort: port, onInterface: interface)
try acceptSocket.listen()
eventLoop.setReader(acceptSocket.fileDescriptor) { [unowned self] in
self.handleNewConnection()
Expand Down
202 changes: 152 additions & 50 deletions Sources/TCPSocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,34 @@

import Foundation

/// Class wrapping around TCP/IPv6 socket

public protocol TCPSocketAddress {

static var family: TCPSocket.Family { get }
static var loopbackName: String { get }

func asData() -> Data

static func allocate() -> Data
static func from(address: String, port: UInt16) throws -> TCPSocketAddress

}

/// Class wrapping around TCP/IP socket
public final class TCPSocket {

public enum Error: Swift.Error {
case unknownFamily
}

public enum Family: Int32 {
case v4
case v6
}


/// The file descriptor number for socket
let family: Family
let fileDescriptor: Int32

/// Whether is this socket in block mode or not
Expand Down Expand Up @@ -61,20 +86,31 @@ public final class TCPSocket {
}
}

init(blocking: Bool = false) throws {
convenience init(boundToPort port: Int, onInterface interface: String, addressReusable: Bool = true) throws {
try self.init(familyOf: interface)
try bind(port: port, interface: interface, addressReusable: addressReusable)
}

convenience init(familyOf address: String) throws {
try self.init(family: Family.from(address: address))
}

init(family: Family, blocking: Bool = false) throws {
self.family = family
#if os(Linux)
let socketType = Int32(SOCK_STREAM.rawValue)
#else
let socketType = SOCK_STREAM
#endif
fileDescriptor = SystemLibrary.socket(AF_INET6, socketType, 0)
fileDescriptor = SystemLibrary.socket(family.constant, socketType, 0)
guard fileDescriptor >= 0 else {
throw OSError.lastIOError()
}
self.blocking = blocking
}

init(fileDescriptor: Int32, blocking: Bool = false) {
init(family: Family, fileDescriptor: Int32, blocking: Bool = false) {
self.family = family
self.fileDescriptor = fileDescriptor
self.blocking = blocking
}
Expand All @@ -83,11 +119,20 @@ public final class TCPSocket {
close()
}

private func addressType() throws -> TCPSocketAddress.Type {
switch family {
case .v4: return sockaddr_in.self
case .v6: return sockaddr_in6.self
}
}

/// Bind the socket at given port and interface
/// - Parameter port: port number to bind to
/// - Parameter interface: networking interface to bind to, in IPv6 format
/// - Parameter addressReusable: should we make address reusable
func bind(port: Int, interface: String = "::", addressReusable: Bool = true) throws {
func bind(port: Int, interface: String? = nil, addressReusable: Bool = true) throws {
let addressType = try self.addressType()
let interface = interface ?? addressType.loopbackName
// make address reusable
if addressReusable {
var reuse = Int32(1)
Expand All @@ -101,21 +146,12 @@ public final class TCPSocket {
throw OSError.lastIOError()
}
}
// create IPv6 socket address
var address = sockaddr_in6()
#if !os(Linux)
address.sin6_len = UInt8(MemoryLayout<sockaddr_in6>.stride)
#endif
address.sin6_family = sa_family_t(AF_INET6)
address.sin6_port = UInt16(port).bigEndian
address.sin6_flowinfo = 0
address.sin6_addr = try ipAddressToStruct(address: interface)
address.sin6_scope_id = 0
let size = socklen_t(MemoryLayout<sockaddr_in6>.size)
// create socket address
var address = try addressType.from(address: interface, port: UInt16(port)).asData()
// bind the address and port on socket
guard withUnsafePointer(to: &address, { pointer in
return pointer.withMemoryRebound(to: sockaddr.self, capacity: Int(size)) { pointer in
return SystemLibrary.bind(fileDescriptor, pointer, size) >= 0
guard address.withUnsafeBytes({ (bytesPointer: UnsafePointer<UInt8>) in
return bytesPointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { pointer in
return SystemLibrary.bind(fileDescriptor, pointer, socklen_t(address.count)) >= 0
}
}) else {
throw OSError.lastIOError()
Expand All @@ -132,38 +168,29 @@ public final class TCPSocket {

/// Accept a new connection
func accept() throws -> TCPSocket {
var address = sockaddr_in6()
var size = socklen_t(MemoryLayout<sockaddr_in6>.size)
let clientFileDescriptor = withUnsafeMutablePointer(to: &address) { pointer in
return pointer.withMemoryRebound(to: sockaddr.self, capacity: Int(size)) { pointer in
var address = try addressType().allocate()
var size = socklen_t(0)
let clientFileDescriptor = address.withUnsafeMutableBytes { (bytesPointer: UnsafeMutablePointer<UInt8>) in
return bytesPointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { pointer in
return SystemLibrary.accept(fileDescriptor, pointer, &size)
}
}
guard clientFileDescriptor >= 0 else {
throw OSError.lastIOError()
}
return TCPSocket(fileDescriptor: clientFileDescriptor)
return TCPSocket(family: family, fileDescriptor: clientFileDescriptor)
}

/// Connect to a peer
/// - Parameter host: the target host to connect, in IPv4 or IPv6 format, like 127.0.0.1 or ::1
/// - Parameter port: the target host port number to connect
func connect(host: String, port: Int) throws {
// create IPv6 socket address
var address = sockaddr_in6()
#if !os(Linux)
address.sin6_len = UInt8(MemoryLayout<sockaddr_in6>.stride)
#endif
address.sin6_family = sa_family_t(AF_INET6)
address.sin6_port = UInt16(port).bigEndian
address.sin6_flowinfo = 0
address.sin6_addr = try ipAddressToStruct(address: host)
address.sin6_scope_id = 0
let size = socklen_t(MemoryLayout<sockaddr_in6>.size)
// connect to the host and port
let connectResult = withUnsafePointer(to: &address) { pointer in
return pointer.withMemoryRebound(to: sockaddr.self, capacity: Int(size)) { pointer in
return SystemLibrary.connect(fileDescriptor, pointer, size)
func connect(host: String? = nil, port: Int) throws {
let addressType = try self.addressType()
let host = host ?? addressType.loopbackName
var address = try addressType.from(address: host, port: UInt16(port)).asData()
let connectResult = address.withUnsafeBytes { (bytesPointer: UnsafePointer<UInt8>) in
return bytesPointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { pointer in
return SystemLibrary.connect(fileDescriptor, pointer, socklen_t(address.count))
}
}
guard connectResult >= 0 || errno == EINPROGRESS else {
Expand Down Expand Up @@ -263,16 +290,6 @@ public final class TCPSocket {
}
}

// Convert IP address to binary struct
private func ipAddressToStruct(address: String) throws -> in6_addr {
// convert interface string into IPv6 address struct
var binary: in6_addr = in6_addr()
guard address.withCString({ inet_pton(AF_INET6, $0, &binary) >= 0 }) else {
throw OSError.lastIOError()
}
return binary
}

private func structToAddress<StructType>(
addrStruct: StructType,
family: Int32,
Expand All @@ -297,3 +314,88 @@ public final class TCPSocket {
return String(data: address, encoding: .utf8)!
}
}


extension TCPSocket.Family {

var constant: Int32 {
switch self {
case .v4: return AF_INET
case .v6: return AF_INET6
}
}

fileprivate static func from(address: String) throws -> TCPSocket.Family {
var binary: in_addr = in_addr()
if (address.withCString { return inet_pton(AF_INET, $0, &binary) } == 1) { return .v4 }
if (address.withCString { return inet_pton(AF_INET6, $0, &binary) } == 1) { return .v6 }
throw TCPSocket.Error.unknownFamily
}

}


extension sockaddr_in: TCPSocketAddress {

public static let family = TCPSocket.Family.v4
public static let loopbackName = "127.0.0.1"

public func asData() -> Data {
var address = self
return withUnsafeBytes(of: &address) {
return Data(bytes: $0.baseAddress!, count: $0.count)
}
}

public static func allocate() -> Data {
return Data(capacity: MemoryLayout<sockaddr_in>.size)
}

// convert interface string into IPv4 address struct
public static func from(address: String, port: UInt16) throws -> TCPSocketAddress {
var binary: in_addr = in_addr()
guard address.withCString({ inet_pton(AF_INET, $0, &binary) >= 0 }) else {
throw OSError.lastIOError()
}
var address = sockaddr_in()
address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
address.sin_family = UInt8(sockaddr_in.family.constant)
address.sin_port = CFSwapInt16HostToBig(port)
address.sin_addr = binary
return address
}

}


extension sockaddr_in6: TCPSocketAddress {

public static let family = TCPSocket.Family.v6
public static let loopbackName = "::"

public func asData() -> Data {
var address = self
return withUnsafeBytes(of: &address) {
return Data(bytes: $0.baseAddress!, count: $0.count)
}
}

public static func allocate() -> Data {
return Data(capacity: MemoryLayout<sockaddr_in6>.size)
}

// convert interface string into IPv6 address struct
public static func from(address: String, port: UInt16) throws -> TCPSocketAddress {
var binary: in6_addr = in6_addr()
guard address.withCString({ inet_pton(AF_INET6, $0, &binary) >= 0 }) else {
throw OSError.lastIOError()
}
var address = sockaddr_in6()
address.sin6_len = UInt8(MemoryLayout<sockaddr_in6>.size)
address.sin6_family = UInt8(sockaddr_in6.family.constant)
address.sin6_port = CFSwapInt16HostToBig(port)
address.sin6_addr = binary
return address
}

}
26 changes: 13 additions & 13 deletions Tests/EmbassyTests/KqueueSelectorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class KqueueSelectorTests: XCTestCase {

func testRegister() {
let selector = try! KqueueSelector()
let socket = try! TCPSocket()
let socket = try! TCPSocket(family: .v6)

XCTAssertNil(selector[socket.fileDescriptor])

Expand All @@ -32,7 +32,7 @@ class KqueueSelectorTests: XCTestCase {

func testUnregister() {
let selector = try! KqueueSelector()
let socket = try! TCPSocket()
let socket = try! TCPSocket(family: .v6)

try! selector.register(socket.fileDescriptor, events: [.read], data: nil)

Expand All @@ -45,7 +45,7 @@ class KqueueSelectorTests: XCTestCase {

func testRegisterKeyError() {
let selector = try! KqueueSelector()
let socket = try! TCPSocket()
let socket = try! TCPSocket(family: .v6)
try! selector.register(socket.fileDescriptor, events: [.read], data: nil)

XCTAssertThrowsError(try selector.register(
Expand All @@ -66,7 +66,7 @@ class KqueueSelectorTests: XCTestCase {

func testUnregisterKeyError() {
let selector = try! KqueueSelector()
let socket = try! TCPSocket()
let socket = try! TCPSocket(family: .v6)

XCTAssertThrowsError(try selector.unregister(socket.fileDescriptor)) { error in
guard let error = error as? KqueueSelector.Error else {
Expand All @@ -84,7 +84,7 @@ class KqueueSelectorTests: XCTestCase {
let selector = try! KqueueSelector()

let port = try! getUnusedTCPPort()
let listenSocket = try! TCPSocket()
let listenSocket = try! TCPSocket(family: .v6)
try! listenSocket.bind(port: port)
try! listenSocket.listen()

Expand All @@ -95,7 +95,7 @@ class KqueueSelectorTests: XCTestCase {
XCTAssertEqual(try! selector.select(timeout: 2.0).count, 0)
}

let clientSocket = try! TCPSocket()
let clientSocket = try! TCPSocket(family: .v6)

// make a connect 1 seconds later
queue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(1 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) {
Expand All @@ -115,15 +115,15 @@ class KqueueSelectorTests: XCTestCase {
let selector = try! KqueueSelector()

let port = try! getUnusedTCPPort()
let listenSocket = try! TCPSocket()
let listenSocket = try! TCPSocket(family: .v6)
try! listenSocket.bind(port: port)
try! listenSocket.listen()

try! selector.register(listenSocket.fileDescriptor, events: [.write], data: nil)

XCTAssertEqual(try! selector.select(timeout: 1.0).count, 0)

let clientSocket = try! TCPSocket()
let clientSocket = try! TCPSocket(family: .v6)
// make a connect 1 seconds later
queue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(1 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) {
try! clientSocket.connect(host: "::1", port: port)
Expand All @@ -137,13 +137,13 @@ class KqueueSelectorTests: XCTestCase {
let selector = try! KqueueSelector()

let port = try! getUnusedTCPPort()
let listenSocket = try! TCPSocket()
let listenSocket = try! TCPSocket(family: .v6)
try! listenSocket.bind(port: port)
try! listenSocket.listen()

try! selector.register(listenSocket.fileDescriptor, events: [.read], data: nil)

let clientSocket = try! TCPSocket()
let clientSocket = try! TCPSocket(family: .v6)
// make a connect 1 seconds later
queue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(1 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)) {
try! clientSocket.connect(host: "::1", port: port)
Expand All @@ -159,7 +159,7 @@ class KqueueSelectorTests: XCTestCase {

try! selector.unregister(listenSocket.fileDescriptor)

let clientSocket2 = try! TCPSocket()
let clientSocket2 = try! TCPSocket(family: .v6)
// make a connect 1 seconds later
queue.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(1 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)
Expand All @@ -177,9 +177,9 @@ class KqueueSelectorTests: XCTestCase {

let port = try! getUnusedTCPPort()

let clientSocket = try! TCPSocket()
let clientSocket = try! TCPSocket(family: .v6)

let listenSocket = try! TCPSocket()
let listenSocket = try! TCPSocket(family: .v6)
try! listenSocket.bind(port: port)
try! listenSocket.listen()

Expand Down
Loading