Related
trying to check whether port open or not in iOS, swift4
socket(AF_INET, SOCK_STREAM, 0)
gives the error: Use of unresolved identifier 'socket'
func isPortOpen(port: in_port_t) -> Bool {
let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)
if socketFileDescriptor == -1 {
return false
}
var addr = sockaddr_in()
let sizeOfSockkAddr = MemoryLayout<sockaddr_in>.size
addr.sin_len = __uint8_t(sizeOfSockkAddr)
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port
addr.sin_addr = in_addr(s_addr: inet_addr("0.0.0.0"))
addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
var bind_addr = sockaddr()
memcpy(&bind_addr, &addr, Int(sizeOfSockkAddr))
if Darwin.bind(socketFileDescriptor, &bind_addr, socklen_t(sizeOfSockkAddr)) == -1 {
return false
}
if listen(socketFileDescriptor, SOMAXCONN ) == -1 {
return false
}
return true
}
I am trying to create an iOS client that sends data to a server on a UDP socket over the device's cellular communication.
Following Does IOS support simultaneous wifi and 3g/4g connections?
link to iOS Multipath BSD Sockets Test, I've tried implementing the solution in Swift 3, that is enumerate network interfaces in the device, identifying the Cellular interface (as suggested in Swift - Get device's IP Address), create a UDP socket and bind it to the sockaddr retrieved from the interface.
Implementation of socket programming in Swift was done by following examples from Socket Programming in Swift: Part 1 - getaddrinfo and following posts.
Unfortunately I received Operation not permitted when trying to send data on the socket, so instead I've tried creating the socket and binding it to the data from getaddrinfo called on a designated port (5555).
That too didn't do the trick.
The interesting thing is that while trying to understand what's wrong, I created a test application for both methods, and when tested for 1000 consecutive create->bind->send->close, about 3-5 of the attempts actually did send the data without the error on either method.
Needless to say this was tested on an actual iPhone.
Quite at a loss, I'd appreciate any advice regarding this.
Code implemented in a static "SocketManager" class (edit: fixed sockaddr allocation size)
// Return IP address String, port String & sockaddr of WWAN interface (pdp_ip0), or `nil`
public static func getInterface() -> (String?, String?, UnsafeMutablePointer<sockaddr>?) {
var host : String?
var service : String?
// Get list of all interfaces on the local machine:
var ifaddr : UnsafeMutablePointer<ifaddrs>?
var clt : UnsafeMutablePointer<sockaddr>?
guard getifaddrs(&ifaddr) == 0 else {
return (nil, nil, clt)
}
guard let firstAddr = ifaddr else {
return (nil, nil, clt)
}
// For each interface ...
for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
let interface = ifptr.pointee
let flags = Int32(ifptr.pointee.ifa_flags)
/// Check for running IPv4 interfaces. Skip the loopback interface.
if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
let addrFamily = interface.ifa_addr.pointee.sa_family
if addrFamily == UInt8(AF_INET) { //Interested in IPv4 for in particular case
// Check interface name:
let name = String(cString: interface.ifa_name)
print("interface name: \(name)")
if name.hasPrefix("pdp_ip") { //cellular interface
// Convert interface address to a human readable string:
let ifa_addr_Value = interface.ifa_addr.pointee
clt = UnsafeMutablePointer<sockaddr>.allocate(capacity: 1)
clt?.initialize(to: ifa_addr_Value, count: 1)
var hostnameBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
var serviceBuffer = [CChar](repeating: 0, count: Int(NI_MAXSERV))
getnameinfo(interface.ifa_addr, socklen_t(ifa_addr_Value.sa_len),
&hostnameBuffer, socklen_t(hostnameBuffer.count),
&serviceBuffer,
socklen_t(serviceBuffer.count),
NI_NUMERICHOST | NI_NUMERICSERV)
host = String(cString: hostnameBuffer)
if let host = host {
print("found host \(String(describing: host))")
}
service = String(cString: serviceBuffer)
if let service = service {
print("found service \(String(describing: service))")
}
break;
}
}
}
}
freeifaddrs(ifaddr)
return (host, service, clt)
}
public static func bindSocket(ip: String, port : String, clt : UnsafeMutablePointer<sockaddr>, useCltAddr : Bool = false) -> Int32 {
print("binding socket for IP: \(ip):\(port) withCltAddr=\(useCltAddr)")
var hints = addrinfo(ai_flags: 0,
ai_family: AF_INET,
ai_socktype: SOCK_DGRAM,
ai_protocol: IPPROTO_UDP,
ai_addrlen: 0,
ai_canonname: nil,
ai_addr: nil,
ai_next: nil)
var connectionInfo : UnsafeMutablePointer<addrinfo>? = nil
let status = getaddrinfo(
ip,
port,
&hints,
&connectionInfo)
if status != 0 {
var strError: String
if status == EAI_SYSTEM {
strError = String(validatingUTF8: strerror(errno)) ?? "Unknown error code"
} else {
strError = String(validatingUTF8: gai_strerror(status)) ?? "Unknown error code"
}
print(strError)
return -1
}
let socketDescriptor = useCltAddr ? socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) : socket(connectionInfo!.pointee.ai_family, connectionInfo!.pointee.ai_socktype, connectionInfo!.pointee.ai_protocol)
if socketDescriptor == -1 {
let strError = String(utf8String: strerror(errno)) ?? "Unknown error code"
let message = "Socket creation error \(errno) (\(strError))"
freeaddrinfo(connectionInfo)
print(message)
return -1
}
let res = useCltAddr ? bind(socketDescriptor, clt, socklen_t(clt.pointee.sa_len)) : bind(socketDescriptor, connectionInfo?.pointee.ai_addr, socklen_t((connectionInfo?.pointee.ai_addrlen)!))
if res != 0 {
let strError = String(utf8String: strerror(errno)) ?? "Unknown error code"
let message = "Socket bind error \(errno) (\(strError))"
freeaddrinfo(connectionInfo)
close(socketDescriptor)
print(message)
return -1
}
freeaddrinfo(connectionInfo)
print("returned socket descriptor \(socketDescriptor)")
return socketDescriptor
}
//returns 0 for failure, 1 for success
public static func sendData(toIP: String, onPort : String, withSocketDescriptor : Int32, data : Data) -> Int{
print("sendData called for targetIP: \(toIP):\(onPort) with socket descriptor: \(withSocketDescriptor)")
var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: MemoryLayout<sockaddr_in>.size)
target.pointee.sin_family = sa_family_t(AF_INET)
target.pointee.sin_addr.s_addr = inet_addr(toIP)
target.pointee.sin_port = in_port_t(onPort)!
var res = 0
data.withUnsafeBytes { (u8Ptr: UnsafePointer<UInt8>) in
let rawPtr = UnsafeRawPointer(u8Ptr)
withUnsafeMutablePointer(to: &target) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
let bytesSent = sendto(withSocketDescriptor, rawPtr, data.count, 0, $0, socklen_t(MemoryLayout.size(ofValue: target)))
if bytesSent > 0 {
print("😄😄😄 Sent \(bytesSent) bytes 😄😄😄")
res = 1
}
if bytesSent == -1 {
let strError = String(utf8String: strerror(errno)) ?? "Unknown error code"
let message = "Socket sendto error \(errno) (\(strError))"
print(message)
}
}
}
}
return res
}
public static func closeSocket(socketDescriptor : Int32, clt : UnsafeMutablePointer<sockaddr>) {
print("closing socket descriptor \(socketDescriptor)")
close(socketDescriptor)
clt.deinitialize()
clt.deallocate(capacity: 1)
}
On ViewController:
override func viewDidLoad() {
super.viewDidLoad()
var i = 0
for _ in 0..<1000 {
i += connectSendClose(withDescriptor: false) // change withDescriptor to switch socket create/bind method
}
print("Sent \(i) packets")
}
private func connectSendClose(withDescriptor : Bool) -> Int {
let interface = SocketManager.getInterface()
guard let ip = interface.0 else {
print("no relevant interface")
return 0
}
guard let clt = interface.2 else {
print("no addr")
return 0
}
let socketDescriptor = SocketManager.bindSocket(ip: ip, port: "5555", clt: clt, useCltAddr: withDescriptor)
if socketDescriptor == -1 {
print("faild to configure socket")
return 0
}
let serverIP = "59.122.442.9" //dummy IP, test was preformed on actual server
let serverPort = "10025" //dummy port, test was preformed on actual server
let input = 42.13
var value = input
let data = withUnsafePointer(to: &value) {
Data(bytes: UnsafePointer($0), count: MemoryLayout.size(ofValue: input))
}
let res = SocketManager.sendData(toIP: serverIP, onPort: serverPort, withSocketDescriptor: socketDescriptor, data: data)
SocketManager.closeSocket(socketDescriptor: socketDescriptor, clt: clt)
return res
}
Edit: Fixed Network byte order bug in creation of target sockadd_in.
Alright, found the problem:
First, as Martin noted, I miss used UnsafeMutablePointer allocation as I took capacity/count parameters as bytes.
This was done also when I allocated sockaddr_in for server details in sendData function (var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: MemoryLayout<sockaddr_in>.size as opposed to var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: 1).
After fixing this to, I started to get better results (about 16 out of 1000 sends passed), but obviously it was not enough.
I Found Send a message using UDP in Swift 3, and decided to change the use of sockaddr_in to var target = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size), sin_family: sa_family_t(AF_INET), sin_port: in_port_t(onPort)!, sin_addr: in_addr(s_addr: inet_addr(toIP)), sin_zero: (0,0,0,0, 0,0,0,0)), everything works.
I'm still puzzled as to why using Unsafe Memory with this struct didn't work though.
Another thing: I moved this code back to my actual App, trying to bind the socket to my own addrinfo via getaddrinfo constantly fails with Can't assign requested address, using the one I get from enumerated interfaces works, but I receive lots of No buffer space available errors (something for another research :).
In the test code, both binding methods (enumerated & getaddrinfo) work fine.
Fixed sendData function:
public static func sendData(toIP: String, onPort : String, withSocketDescriptor : Int32, data : Data) -> Int{
print("sendData called for targetIP: \(toIP):\(onPort) with socket descriptor: \(withSocketDescriptor)")
var target = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size), sin_family: sa_family_t(AF_INET), sin_port: in_port_t(bigEndian: onPort)!, sin_addr: in_addr(s_addr: inet_addr(toIP)), sin_zero: (0,0,0,0, 0,0,0,0))
var res = 0
data.withUnsafeBytes { (u8Ptr: UnsafePointer<UInt8>) in
let rawPtr = UnsafeRawPointer(u8Ptr)
withUnsafeMutablePointer(to: &target) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
let bytesSent = sendto(withSocketDescriptor, rawPtr, data.count, 0, $0, socklen_t(MemoryLayout.size(ofValue: target)))
if bytesSent > 0 {
print("😄😄😄 Sent \(bytesSent) bytes 😄😄😄")
res = 1
}
if bytesSent == -1 {
let strError = String(utf8String: strerror(errno)) ?? "Unknown error code"
let message = "Socket sendto error \(errno) (\(strError))"
print(message)
}
}
}
}
return res
}
Just trying Bonjour in swift 3
Here is my code , I can receive the delegate
func netServiceDidResolveAddress(_ sender: NetService) {
print("netServiceDidResolveAddress service name \(sender.name) of type \(sender.type)," +
"port \(sender.port), addresses \(sender.addresses)")
}
And here is my result
netServiceDidResolveAddress service name Webber's Mac mini of type
_myapp._tcp.,port 5678, addresses Optional([<1002162e c0a80205 00000000 00000000>, <1c1e162e 00000000 fe800000 00000000 00bce7ad
24b4b7e8 08000000>])
c0a80205 is the IP I looking for => 192.168.2.5
And the address is [Data] , Apple says
The addresses of the service. This is an NSArray of NSData instances,
each of which contains a single struct sockaddr suitable for use with
connect(2). In the event that no addresses are resolved for the
service or the service has not yet been resolved, an empty NSArray is
returned.
I still confuse why Data can't use .btyes ?
As Apple says "This is an NSArray of NSData instances"
But I can't use it like NSData
And how to resolve the address as readable IP string ?
I try this before , but do not get the result as I except ...
let thedata = NSData(bytes: sender.addresses, length: (sender.addresses?.count)!)
var storage = sockaddr_storage()
thedata.getBytes(&storage, length: sizeof(sockaddr_storage))
if Int32(storage.ss_family) == AF_INET {
let addr4 = withUnsafePointer(&storage) {UnsafePointer<sockaddr_in>($0).pointee }
print(inet_ntoa(addr4.sin_addr));
}
Any suggestion will be help , Thanks
Here's how I did it in Swift 3.
func netServiceDidResolveAddress(_ sender: NetService) {
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
guard let data = sender.addresses?.first else { return }
data.withUnsafeBytes { (pointer:UnsafePointer<sockaddr>) -> Void in
guard getnameinfo(pointer, socklen_t(data.count), &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 else {
return
}
}
let ipAddress = String(cString:hostname)
print(ipAddress)
}
Swift 5
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
data.withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> Void in
let sockaddrPtr = pointer.bindMemory(to: sockaddr.self)
guard let unsafePtr = sockaddrPtr.baseAddress else { return }
guard getnameinfo(unsafePtr, socklen_t(data.count), &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 else {
return
}
}
let ipAddress = String(cString:hostname)
print(ipAddress)
Edited Phil Coles answer for Swift 5.0 warning free solution:
func netServiceDidResolveAddress(_ sender: NetService) {
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
guard let data = sender.addresses?.first else { return }
data.withUnsafeBytes { ptr in
guard let sockaddr_ptr = ptr.baseAddress?.assumingMemoryBound(to: sockaddr.self) else {
// handle error
return
}
var sockaddr = sockaddr_ptr.pointee
guard getnameinfo(sockaddr_ptr, socklen_t(sockaddr.sa_len), &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 else {
return
}
}
let ipAddress = String(cString:hostname)
print(ipAddress)
}
OK ... this is not a smart answer , at least I can get the readable IP
Just use this func to get IP string
let bonjourDevices = [NetService]()
let bonjourDevice = bonjourDevices[0]
let host = self.getIPV4StringfromAddress(address:bonjourDevice.addresses!)
func getIPV4StringfromAddress(address: [Data] , port : Int ) -> String{
let data = address.first! as NSData;
var ip1 = UInt8(0)
data.getBytes(&ip1, range: NSMakeRange(4, 1))
var ip2 = UInt8(0)
data.getBytes(&ip2, range: NSMakeRange(5, 1))
var ip3 = UInt8(0)
data.getBytes(&ip3, range: NSMakeRange(6, 1))
var ip4 = UInt8(0)
data.getBytes(&ip4, range: NSMakeRange(7, 1))
let ipStr = String(format: "%d.%d.%d.%d:%d",ip1,ip2,ip3,ip4,port);
return ipStr;
}
I can't make it work with Data, but using NSData, I would use this:
extension NSData {
func castToCPointer<T>() -> T {
let mem = UnsafeMutablePointer<T>.allocate(capacity: MemoryLayout<T.Type>.size)
self.getBytes(mem, length: MemoryLayout<T.Type>.size)
return mem.move()
}
}
So we have netServiceDidResolveAddress:
func netServiceDidResolveAddress(_ sender: NetService) {
if let addresses = sender.addresses, addresses.count > 0 {
for address in addresses {
let data = address as NSData
let inetAddress: sockaddr_in = data.castToCPointer()
if inetAddress.sin_family == __uint8_t(AF_INET) {
if let ip = String(cString: inet_ntoa(inetAddress.sin_addr), encoding: .ascii) {
// IPv4
print(ip)
}
} else if inetAddress.sin_family == __uint8_t(AF_INET6) {
let inetAddress6: sockaddr_in6 = data.castToCPointer()
let ipStringBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: Int(INET6_ADDRSTRLEN))
var addr = inetAddress6.sin6_addr
if let ipString = inet_ntop(Int32(inetAddress6.sin6_family), &addr, ipStringBuffer, __uint32_t(INET6_ADDRSTRLEN)) {
if let ip = String(cString: ipString, encoding: .ascii) {
// IPv6
print(ip)
}
}
ipStringBuffer.deallocate(capacity: Int(INET6_ADDRSTRLEN))
}
}
}
}
I am having the following result (storing ips in array before display):
["172.16.10.120", "172.16.8.251", "::", "::82c9:d9a5:2eed:1c87"]
Code inspired by https://gist.github.com/agrippa1994/d8c66a2ded74fb2dd801 written in Swift 2.3 and adapted for Swift 3.0
can someone tell me how to find out in Swift2 if Port is busy?
Because I write a mac app that has a self written Tcp server, but sometimes it wont start up because it "cant bind to port". So how can I check if the port is not used, to block the start button of the Tcp server until the port is free, again?
And I do not want a new framework.
Thanks
mainly code taken from Swifter: https://github.com/glock45/swifter
func checkTcpPortForListen(port: in_port_t) -> (Bool, descr: String){
let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)
if socketFileDescriptor == -1 {
return (false, "SocketCreationFailed, \(descriptionOfLastError())")
}
var addr = sockaddr_in()
addr.sin_len = __uint8_t(sizeof(sockaddr_in))
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port
addr.sin_addr = in_addr(s_addr: inet_addr("0.0.0.0"))
addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
var bind_addr = sockaddr()
memcpy(&bind_addr, &addr, Int(sizeof(sockaddr_in)))
if bind(socketFileDescriptor, &bind_addr, socklen_t(sizeof(sockaddr_in))) == -1 {
let details = descriptionOfLastError()
release(socketFileDescriptor)
return (false, "\(port), BindFailed, \(details)")
}
if listen(socketFileDescriptor, SOMAXCONN ) == -1 {
let details = descriptionOfLastError()
release(socketFileDescriptor)
return (false, "\(port), ListenFailed, \(details)")
}
release(socketFileDescriptor)
return (true, "\(port) is free for use")
}
func release(socket: Int32) {
Darwin.shutdown(socket, SHUT_RDWR)
close(socket)
}
func descriptionOfLastError() -> String {
return String.fromCString(UnsafePointer(strerror(errno))) ?? "Error: \(errno)"
}
Update of the correct answer for Swift 4:
func checkTcpPortForListen(port: in_port_t) -> (Bool, descr: String) {
let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)
if socketFileDescriptor == -1 {
return (false, "SocketCreationFailed, \(descriptionOfLastError())")
}
var addr = sockaddr_in()
let sizeOfSockkAddr = MemoryLayout<sockaddr_in>.size
addr.sin_len = __uint8_t(sizeOfSockkAddr)
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port
addr.sin_addr = in_addr(s_addr: inet_addr("0.0.0.0"))
addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
var bind_addr = sockaddr()
memcpy(&bind_addr, &addr, Int(sizeOfSockkAddr))
if Darwin.bind(socketFileDescriptor, &bind_addr, socklen_t(sizeOfSockkAddr)) == -1 {
let details = descriptionOfLastError()
release(socket: socketFileDescriptor)
return (false, "\(port), BindFailed, \(details)")
}
if listen(socketFileDescriptor, SOMAXCONN ) == -1 {
let details = descriptionOfLastError()
release(socket: socketFileDescriptor)
return (false, "\(port), ListenFailed, \(details)")
}
release(socket: socketFileDescriptor)
return (true, "\(port) is free for use")
}
func release(socket: Int32) {
Darwin.shutdown(socket, SHUT_RDWR)
close(socket)
}
func descriptionOfLastError() -> String {
return String.init(cString: (UnsafePointer(strerror(errno))))
}
EDIT:
example for calling this function:
var portNum: UInt16 = 0
for i in 50000..<65000 {
let (isFree, _) = checkTcpPortForListen(port: UInt16(i))
if isFree == true {
portNum = UInt16(i)
break;
}
}
I'm trying to use Secure Transport with BSD sockets using Swift. It seems like it should be simple enough, but I can't get it to work and documentation on the subject is scarce.
I've boiled my issue down to a simple "Socket" class, where I've (to the best of my knowledge) fulfilled the requirements of Secure Transport.
import Cocoa
class Socket: NSObject {
private let hello = "Hello!"
private var socketfd: Int32
private var sock_addr: sockaddr
private var sslContext: SSLContext?
var sslWriteCallbackFunc: SSLWriteFunc {
get {
let ump = UnsafeMutablePointer<((SSLConnectionRef, UnsafePointer<Void>,
UnsafeMutablePointer<Int>) -> OSStatus)>.alloc(1)
ump.initialize(sslWriteCallback)
return CFunctionPointer<((SSLConnectionRef, UnsafePointer<Void>,
UnsafeMutablePointer<Int>) -> OSStatus)>(COpaquePointer(ump))
}
}
var sslReadCallbackFunc: SSLReadFunc {
get {
let ump = UnsafeMutablePointer<((SSLConnectionRef, UnsafeMutablePointer<Void>,
UnsafeMutablePointer<Int>) -> OSStatus)>.alloc(1)
ump.initialize(sslReadCallback)
return CFunctionPointer<((SSLConnectionRef, UnsafeMutablePointer<Void>,
UnsafeMutablePointer<Int>) -> OSStatus)>(COpaquePointer(ump))
}
}
init(address: String, port: UInt16) {
socketfd = Darwin.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
var addr = Darwin.sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)), sin_family: sa_family_t(AF_INET), sin_port: CFSwapInt16(port), sin_addr: in_addr(s_addr: inet_addr(address)), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
sock_addr = Darwin.sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
Darwin.memcpy(&sock_addr, &addr, Int(sizeof(sockaddr_in)))
super.init()
}
func connect() -> Socket {
let err = Darwin.connect(socketfd, &sock_addr, socklen_t(sizeof(sockaddr_in)))
return self
}
func makeSecure() -> Socket {
if let umc = SSLCreateContext(nil, kSSLClientSide, kSSLStreamType) {
sslContext = umc.takeRetainedValue()
var status = SSLSetIOFuncs(sslContext!, sslReadCallbackFunc, sslWriteCallbackFunc)
status = SSLSetConnection(sslContext!, &socketfd)
SSLHandshake(sslContext!)
}
return self
}
func sendHello() -> Socket {
let bytes = [UInt8](hello.utf8)
let data = NSData(bytes: bytes, length: bytes.count)
let test = UnsafeMutablePointer<Int>.alloc(1)
test.initialize(bytes.count)
self.sslWriteCallback(&socketfd, data: data.bytes, dataLength: test)
return self
}
// MARK: - SSL Callback Methods
func sslReadCallback(connection: SSLConnectionRef,
data: UnsafeMutablePointer<Void>,
dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
let bytesRead = read(socketfd, data, UnsafePointer<Int>(dataLength).memory)
return noErr
}
func sslWriteCallback(connection: SSLConnectionRef,
data: UnsafePointer<Void>,
dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
let sent = Darwin.sendto(socketfd, data, UnsafePointer<Int>(dataLength).memory, 0, &sock_addr, socklen_t(sizeof(sockaddr_in)))
if (sent < 0) {
let error = NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
println(error.localizedDescription)
} else {
println("Sent \(sent) bytes (\(hello))")
}
return noErr
}
}
I've tested the non-TLS socket communication by making a simple instance:
let socket = Socket(address: "some-ip-address", port: 8080)
socket.connect().sendHello()
and running an echo server on the target machine using netcat. This works fine.
nc -l -p 8080
Trying to wrap the socket in Secure Transport's TLS (calling the makeSecure() method) crashes at the call to SSLHandshake(...) with a EXC_BAD_ADDRESS(code=2, address=...) error. Does anyone have any pointers as to what it is I'm missing here?
EDIT
I can see Console puts out:
04/06/15 09:20:48,000 kernel[0]: Data/Stack execution not permitted: TheProject[pid 29184] at virtual address 0x100602000, protections were read-write
EDIT 2
I got it working with Swift 2 in the Xcode 7 beta. See below.
Starting with Swift 2 included with the Xcode 7 beta, Function Pointers in Swift work and have been greatly simplified. I turned my example above into this, which works:
import Foundation
func sslReadCallback(connection: SSLConnectionRef,
data: UnsafeMutablePointer<Void>,
var dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
let socketfd = UnsafePointer<Int32>(connection).memory
let bytesRequested = dataLength.memory
let bytesRead = read(socketfd, data, UnsafePointer<Int>(dataLength).memory)
if (bytesRead > 0) {
dataLength = UnsafeMutablePointer<Int>.alloc(1)
dataLength.initialize(bytesRead)
if bytesRequested > bytesRead {
return Int32(errSSLWouldBlock)
} else {
return noErr
}
} else if (bytesRead == 0) {
dataLength = UnsafeMutablePointer<Int>.alloc(1)
dataLength.initialize(0)
return Int32(errSSLClosedGraceful)
} else {
dataLength = UnsafeMutablePointer<Int>.alloc(1)
dataLength.initialize(0)
switch (errno) {
case ENOENT: return Int32(errSSLClosedGraceful)
case EAGAIN: return Int32(errSSLWouldBlock)
case ECONNRESET: return Int32(errSSLClosedAbort)
default: return Int32(errSecIO)
}
}
}
func sslWriteCallback(connection: SSLConnectionRef,
data: UnsafePointer<Void>,
var dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
let socketfd = UnsafePointer<Int32>(connection).memory
let bytesToWrite = dataLength.memory
let bytesWritten = write(socketfd, data, UnsafePointer<Int>(dataLength).memory)
if (bytesWritten > 0) {
dataLength = UnsafeMutablePointer<Int>.alloc(1)
dataLength.initialize(bytesWritten)
if (bytesToWrite > bytesWritten) {
return Int32(errSSLWouldBlock)
} else {
return noErr
}
} else if (bytesWritten == 0) {
dataLength = UnsafeMutablePointer<Int>.alloc(1)
dataLength.initialize(0)
return Int32(errSSLClosedGraceful)
} else {
dataLength = UnsafeMutablePointer<Int>.alloc(1)
dataLength.initialize(0)
if (EAGAIN == errno) {
return Int32(errSSLWouldBlock)
} else {
return Int32(errSecIO)
}
}
}
var socketfd = Darwin.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
var addr = Darwin.sockaddr_in(sin_len: __uint8_t(sizeof(sockaddr_in)), sin_family: sa_family_t(AF_INET), sin_port: CFSwapInt16(8080), sin_addr: in_addr(s_addr: inet_addr("192.168.0.113")), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
var sock_addr = Darwin.sockaddr(sa_len: 0, sa_family: 0, sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
Darwin.memcpy(&sock_addr, &addr, Int(sizeof(sockaddr_in)))
var err = Darwin.connect(socketfd, &sock_addr, socklen_t(sizeof(sockaddr_in)))
if let umc = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType) {
var sslContext = umc.takeRetainedValue()
SSLSetIOFuncs(sslContext, sslReadCallback, sslWriteCallback)
SSLSetConnection(sslContext, &socketfd)
SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnClientAuth, Boolean(1))
SSLHandshake(sslContext)
}
The answer by Hans appears to allocate memory unnecessarily. The following is a Swift 3.1 version with more error checking and URL support and grabs the common name to return (rather than actually reading or writing data).
func getCNforSSL(at url:URL, port:UInt16) -> String? {
var socketfd = Darwin.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
guard let ip = urlToIP(url) else {
NSLog("Could not get IP from URL \(url)")
return nil
}
let inAddr = in_addr(s_addr: inet_addr(ip))
var addr = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
sin_family: sa_family_t(AF_INET),
sin_port: CFSwapInt16(port),
sin_addr: inAddr,
sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
var sock_addr = sockaddr(sa_len: 0,
sa_family: 0,
sa_data: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
_ = memcpy(&sock_addr, &addr, MemoryLayout<sockaddr_in>.size)
guard connect(socketfd, &sock_addr, socklen_t(MemoryLayout<sockaddr_in>.size)) == 0 else {
NSLog("Failed connection for \(url) port \(port) with error \(Darwin.errno)")
return nil
}
defer {
if close(socketfd) != 0 {
NSLog("Error closing socket for \(url) port \(port) with error \(Darwin.errno)")
}
}
guard let sslContext = SSLCreateContext(kCFAllocatorDefault, .clientSide, .streamType) else {
NSLog("Could not create SSL Context for \(url) port \(port)")
return nil
}
defer {
SSLClose(sslContext)
}
SSLSetIOFuncs(sslContext, sslReadCallback, sslWriteCallback)
SSLSetConnection(sslContext, &socketfd)
SSLSetSessionOption(sslContext, .breakOnServerAuth, true)
var secTrust:SecTrust? = nil
var status:OSStatus = 0
var subject:String? = nil
repeat {
status = SSLHandshake(sslContext)
if status == errSSLPeerAuthCompleted {
SSLCopyPeerTrust(sslContext, &secTrust)
if let trust = secTrust {
// 0 always garunteed to exist
let cert = SecTrustGetCertificateAtIndex(trust, 0)!
subject = SecCertificateCopySubjectSummary(cert) as String?
}
}
} while status == errSSLWouldBlock
guard status == errSSLPeerAuthCompleted else {
NSLog("SSL Handshake Error for \(url) port \(port) OSStatus \(status)")
return nil
}
return subject
}
func sslReadCallback(connection: SSLConnectionRef,
data: UnsafeMutableRawPointer,
dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
let socketfd = connection.load(as: Int32.self)
let bytesRequested = dataLength.pointee
let bytesRead = read(socketfd, data, UnsafePointer<Int>(dataLength).pointee)
if (bytesRead > 0) {
dataLength.initialize(to: bytesRead)
if bytesRequested > bytesRead {
return Int32(errSSLWouldBlock)
} else {
return noErr
}
} else if (bytesRead == 0) {
dataLength.initialize(to: 0)
return Int32(errSSLClosedGraceful)
} else {
dataLength.initialize(to: 0)
switch (errno) {
case ENOENT: return Int32(errSSLClosedGraceful)
case EAGAIN: return Int32(errSSLWouldBlock)
case ECONNRESET: return Int32(errSSLClosedAbort)
default: return Int32(errSecIO)
}
}
}
func sslWriteCallback(connection: SSLConnectionRef,
data: UnsafeRawPointer,
dataLength: UnsafeMutablePointer<Int>) -> OSStatus {
let socketfd = connection.load(as: Int32.self)
let bytesToWrite = dataLength.pointee
let bytesWritten = write(socketfd, data, UnsafePointer<Int>(dataLength).pointee)
if (bytesWritten > 0) {
dataLength.initialize(to: bytesWritten)
if (bytesToWrite > bytesWritten) {
return Int32(errSSLWouldBlock)
} else {
return noErr
}
} else if (bytesWritten == 0) {
dataLength.initialize(to: 0)
return Int32(errSSLClosedGraceful)
} else {
dataLength.initialize(to: 0)
if (EAGAIN == errno) {
return Int32(errSSLWouldBlock)
} else {
return Int32(errSecIO)
}
}
}
private func urlToIP(_ url:URL) -> String? {
guard let hostname = url.host else {
return nil
}
guard let host = hostname.withCString({gethostbyname($0)}) else {
return nil
}
guard host.pointee.h_length > 0 else {
return nil
}
var addr = in_addr()
memcpy(&addr.s_addr, host.pointee.h_addr_list[0], Int(host.pointee.h_length))
guard let remoteIPAsC = inet_ntoa(addr) else {
return nil
}
return String.init(cString: remoteIPAsC)
}
I asked a network guru peer your question; this was his response:
This person is SOL because Secure Transport requires that you implement C function callbacks and Swift does not currently support that .
I recommend that developer use CFSocketStream, which takes care of TLS and is easily callable from Swift. See the TLSTool sample code.
https://developer.apple.com/library/mac/samplecode/SC1236/