Using BSD Sockets in Swift Sep 24 2019

Apple provides many useful network frameworks. Network.framework (you can check my previous article on Network.framework if you want to see an example) is the latest. But sometimes we need to go deeper, and the abstractions might get in the way. In this post, we are going to see how to use BSD sockets directly in Swift.

Using BSD sockets means interfacing with C from Swift. Let’s first have a look at some concepts and tips that will help us with using sockets.

** You can check the full code of the server in the GitHub repository.

C interoperability

In macOS we have access to BSD sockets, via the C socket(2) function. Using the socket syscall will require us to interoperate with C code, which will inevitably bring us to pointers.

In C we are in charge of memory management, which makes it very flexible but error-prone. One of Swift’s strengths is that memory management is taken care for us by the language (ARC, etc.). Calling C functions in Swift is as easy as any other function call. Things start to get trickier when we have to deal with pointers. The compiler will try to keep us safe, so we will have to make some accommodations.

When we use pointers in Swift, we use one of its eight types of pointers:

Pointers are marked as Unsafe to make clear we are treading in territory that the compiler can not keep us entirely safe. When we see Mutable, it means that we can change the pointer’s pointee content. When we see Raw, it means that we are pointing directly to memory address without any particular representation. And Buffer means that the pointer “points” to a collection of elements(e.g. an array of chars). That should give us an idea of which type to use, but in reality, the Compiler will let you know when you are using the incorrect type (Which makes things easy).

Another common scenario we might find ourselves in when working with C functions is pointer casting. We get a pointer to one structure, and that structure can be interchanged by another structure (That sounds complicated, but think about casting one struct type to another). The problem here is that the compiler will not let us use our structure because it is defined as a specific type. We will need to cast it to the other equivalent type somehow. If we are completely sure of what we are doing, we use the function withUnsafe[PointerType]. The function withUnsafe[Mutable][Raw][Buffer]Pointer allows us to get a reference to our pointer and use it inside a closure. Now we can call withMemoryRebound on our new pointer. The function withMemoryRebound allows us to get a temporary pointer of a specific type and execute a closure that has access to that temporary pointer. Let’s see an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
# lets cast our sockaddr_in6 to sockaddr
var address = sockaddr_in6(sin6_len: UInt8(MemoryLayout<sockaddr_in6>.size),
                           sin6_family: sa_family_t(PF_INET6),
                           sin6_port: port,
                           sin6_flowinfo: 0,
                           sin6_addr: in6addr_any,
                           sin6_scope_id: 0)

let result = withUnsafePointer(to: &address) {
  $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
    bind(bsdSocket, $0, socklen_t(MemoryLayout<sockaddr_in6>.stride))
  }
}

Here bind expects a pointer to a sockaddr but we have a sockaddr_in6. Our address variable has more info than a regular sockaddr, but if we were to treat it as a sockaddr, it would have the required info a sockaddr structure has.

Also, note that the $0 in each closure references a different object, we are trying to be succinct and not declare a temporary variable (e.g. { newPointer in ... }) so we are only using the default $0. The variable $0 is an autogenerated variable that points to the first parameter of our closure.

We don’t need to use this technique often. We only use it if we are sure that our structure can be used as a different structure type. We have to be careful. If our structures are not interchangeable, we might cause some odd behaviour (i.e. not necessarily a crash, but a weird result).

Another useful concept to know when dealing with pointers in Swift is pointee. ̇When we have a pointer we can access the object it points to by using the pointee function. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var addressInfo: UnsafeMutablePointer<addrinfo>? = nil
/*
addrinfo has the following interface:
public struct addrinfo {

    public var ai_flags: Int32 /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */

    public var ai_family: Int32 /* PF_xxx */

    public var ai_socktype: Int32 /* SOCK_xxx */

    public var ai_protocol: Int32 /* 0 or IPPROTO_xxx for IPv4 and IPv6 */

    public var ai_addrlen: socklen_t /* length of ai_addr */

    public var ai_canonname: UnsafeMutablePointer<Int8>! /* canonical name for hostname */

    public var ai_addr: UnsafeMutablePointer<sockaddr>! /* binary address */

    public var ai_next: UnsafeMutablePointer<addrinfo>! /* next structure in linked list */

    public init()

    public init(ai_flags: Int32, ai_family: Int32, ai_socktype: Int32, ai_protocol: Int32, ai_addrlen: socklen_t, ai_canonname: UnsafeMutablePointer<Int8>!, ai_addr: UnsafeMutablePointer<sockaddr>!, ai_next: UnsafeMutablePointer<addrinfo>!)
}
*/
function_that_initializes_our_pointer(&addressInfo)
print(addressInfo!.pointee.ai_family)

Ok, that should be enough for us to work with native sockets from Swift.

If you find yourself working a lot with C code from Swift, you should read Apple’s documentation on C Interoperability with Swift.

Ok, let’s start working with sockets.

Using sockets in Swift

A socket is a structure that allows us to communicate two processes. It can be in the same machine or remote. Nowadays, we usually think that sockets are only used for Internet connections. But sockets can also be used as Inter-Process-Communication (IPC) structures. We are going to focus on sockets for Internet connections. I encourage you to check how sockets could also be used to create IPC behaviour, for example, pipes(using AF_UNIX).

In general, when we are working with sockets we’ll have in one end a Server and in the other a Client. Depending on the use we’ll give a socket, the creation and setup changes.

If we are going to create a socket as a client that connects with a server, we would have the following workflow:

If we need a socket for a server, the general build and setup will look like the following:

Alright, let’s build a TCP server using BSD sockets.

Echo Server using BSD sockets

We are going to build a command-line tool using the Swift Package Manager. So let’s create it.

1
2
3
$ mkdir echoSocketServer
$ cd echoSocketServer
$ swift package init --type executable

Now our main will only be the entry point for our server. Edit it and add the following content:

1
2
3
4
5
6
7
8
9
import Foundation

print("Welcome to our simple echo server!")

var server = Server()
server.start()

RunLoop.main.run()
exit(EXIT_SUCCESS)

Let’s now create the Server class. Create a new file Server.swift inside the sources directory, and add the following content.

1
2
3
4
5
6
7
8
9
10
11
import Foundation

class Server {

  let servicePort = "1234"

  func start() {
    print("Server starting...")
  }

}

Now we can build and run it. We should see our message “Server starting…”. You can stop the program pressing [Ctrl+C].

1
2
3
4
# run it on your application's root directory
$ swift run
#Welcome to our simple echo server!
#Server starting...

Good, we have our basic program working, now we can focus on learning about sockets.

Steps to build our server socket

Remember the steps we defined for a server socket at the begining of the post:

We are going to be working on the start function in our Server. I’ll show you blocks of the code that we are working on so we can focus on the details for each step of the process. At the end of this section, you’ll find the complete code for the Server.

Let’s begin.

1. Creating the socket

Creating the socket is the easy part. Let’s declare the socket and initialize it. We are going to declare a socket that uses the IPV6 family of protocols and uses TCP:

1
2
3
4
5
6
7
8
 let socketFD = socket(AF_INET6, //Domain e.g. [AF_INET,AF_INET6, AF_UNIX]
                       SOCK_STREAM, //Type e.g. [SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RAW]
                       IPPROTO_TCP  //Protocol e.g. [IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP]
                       )//Return a FileDescriptor -1 = error
 if socketFD == -1 {
   print("Error creating BSD Socket")
   return
 }

The socket domain specifies the protocol family that the socket will use. Some examples are:

The typical types you’ll see are:

Finally, we define the protocol. Again, I’ll list some common protocols that you might recognize:

2. Define the socket address structure

The second step would be to create the socket address structure. Here we see a difference between using IPV4 and IPV6. Each has a different socket address structure. An abstraction exists here that is used to “emulate” something similar to inheritance. We have the following structures:

1
2
3
4
5
struct sockaddr {
    unsigned char     sa_len;    /* total length */
    sa_family_t    sa_family;    /* address family */
    char         sa_data[14];    /* actually longer; address value */
};

sockaddr is the structure that most of the kernel functions expect, but there are two other structs that represent IPV4 and IPV6 specific cases.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 * Socket address, internet style.
 * Expected in IPV4
 */
struct sockaddr_in {
    uint8_t        sin_len;         /* length of this struct */
    sa_family_t    sin_family;    /* AF_INET */
    in_port_t    sin_port;        /* Transport layer port # */
    struct    in_addr sin_addr;  /* IPV4 address */
    char    sin_zero[8];         /* Structure padding */
};
/*
 * Expected in IPV6
 */
struct sockaddr_in6 {
    uint8_t        sin6_len;            /* length of this struct */
    sa_family_t    sin6_family;      /* AF_INET6 */
    in_port_t    sin6_port;          /* Transport layer port # */
    uint32_t    sin6_flowinfo;      /* IP6 flow information */
    struct in6_addr    sin6_addr;  /* IP6 address */
    uint32_t    sin6_scope_id;      /* scope zone index */
};

The required fields are the ones represented by sockaddr. If we get a sockaddr and we want to know if it is an in or in6, we can check the sa_family_t and also the length.

We rarely define the sockaddr_t directly, only in specific cases that we are sure we require one or the other. The general “best practice” is to use the getaddrinfo function. Here is the signature of the getaddrinfo function:

1
2
3
int getaddrinfo(const char *node, const char *service,
                       const struct addrinfo *hints,
                       struct addrinfo **res);

Let’s use getaddrinfo to obtain our sockaddr structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    var hints = addrinfo(
      ai_flags: AI_PASSIVE,       // AI_PASSIVE in conjuntion with a nil node in getaddrinfo function, makes sure that the returned socket is suitable for binding a socket that accept connections.
      ai_family: AF_UNSPEC,       // Either IPv4 or IPv6
      ai_socktype: SOCK_STREAM,   // TCP
      ai_protocol: 0,
      ai_addrlen: 0,
      ai_canonname: nil,
      ai_addr: nil,
      ai_next: nil)

    var servinfo: UnsafeMutablePointer<addrinfo>? = nil
    let addrInfoResult = getaddrinfo(
      nil,                        // Any interface
      socketPort,                 // The port on which will be listenend
      &hints,                     // Protocol configuration as per above
      &servinfo)

    if addrInfoResult != 0 {
      print("Error getting address info: \(errno)")
      return
    }

Now we have in serverinfo the ai_addr pointing to a sockaddr structure that we can use in the bind function. Let’s bind our socket to the sockaddr.

3. Binding the socket to the address.

Binding is an easy process. We only need to call bind with our socket and sockaddr. Here is the signature of bind:

1
2
int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

The first parameter is the file descriptor we obtained when we created the socket. The second parameter is a pointer to our sockaddr structure. And last, we need to pass the lenght of our sockaddr.

1
2
3
4
5
6
    let bindResult = bind(socketFD, servinfo!.pointee.ai_addr, socklen_t(servinfo!.pointee.ai_addrlen))

    if bindResult == -1 {
      print("Error binding socket to Address: \(errno)")
      return
    }

4. Prepare to listen for connections

Before we are ready to accept connections on our socket, we need to prepare it. We use the listen function to set up the socket that is going to be accepting connections and also define the queue limit for the incoming connections. Here is the signature of listen:

1
int listen(int s, int backlog);

The first parameter is the file descriptor we obtained when we created our socket. The second parameter, backlog, defines how big the queue of incomming connections can be before connections start being dropped. The function returns -1 for error. In this and the previous functions, we can check errno for error code.

Let’s prepare our socket for listening to connections:

1
2
3
4
5
6
7
8
 let listenResult = listen(socketFD, //Socket File descriptor
                           8         // The backlog argument defines the maximum length the queue of pending connections may grow to
 )

 if listenResult == -1 {
   print("Error setting our socket to listen")
   return
 }

Now we can finally accept a connection.

5. Accept a connection

We are going to create a never ending loop that will accept connections. The server will be sending back the message it received from the client as an echo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 while (true) {
   let MTU = 65536
   var addr = sockaddr()
   var addr_len :socklen_t = 0

   print("About to accept")
   let clientFD = accept(socketFD, &addr, &addr_len)
   print("Accepted new client with file descriptor: \(clientFD)")

   if clientFD == -1 {
     print("Error accepting connection")
   }

   var buffer = UnsafeMutableRawPointer.allocate(byteCount: MTU,alignment: MemoryLayout<CChar>.size)

   while(true) {
     let readResult = read(clientFD, &buffer, MTU)

     if (readResult == 0) {
       break;  // end of file
     } else if (readResult == -1) {
       print("Error reading form client\(clientFD) - \(errno)")
       break;  // error
     } else {
       let strResult = withUnsafePointer(to: &buffer) {
         $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: readResult)) {
           String(cString: $0)
         }
       }
       print("Received form client(\(clientFD)): \(strResult)")
       write(clientFD, &buffer, readResult)
     }
   }
 }

Alright, there is a lot to take here, so let’s break it down. First, we declared MTU that will be the Maximum Transmition Unit that we are expecting. Our Server can only process clients syncronously, that means, we will only serve one client at a time. I didn’t want to add more complexity to our example using threads or GCD.

We now wait for a client to try to connect to us using accept. Once a client has connected, we’ll obtain a file descriptor from where we can read and write. We are going to create a variable named buffer to put the data we read from our client. And we are going to enter a cycle to read and echo whatever the client sends.

Here is whole Server.swift file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import Foundation

class Server {

  let servicePort = "1234"

  func start() {
    print("Server starting...")

    let socketFD = socket(AF_INET6, //Domain [AF_INET,AF_INET6, AF_UNIX]
                          SOCK_STREAM, //Type [SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RAW]
                          IPPROTO_TCP  //Protocol [IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP]
                          )//Return a FileDescriptor -1 = error
    if socketFD == -1 {
      print("Error creating BSD Socket")
      return
    }

    var hints = addrinfo(
      ai_flags: AI_PASSIVE,       // Assign the address of the local host to the socket structures
      ai_family: AF_UNSPEC,       // Either IPv4 or IPv6
      ai_socktype: SOCK_STREAM,   // TCP
      ai_protocol: 0,
      ai_addrlen: 0,
      ai_canonname: nil,
      ai_addr: nil,
      ai_next: nil)

    var servinfo: UnsafeMutablePointer<addrinfo>? = nil
    let addrInfoResult = getaddrinfo(
      nil,                        // Any interface
      servicePort,                   // The port on which will be listenend
      &hints,                     // Protocol configuration as per above
      &servinfo)

    if addrInfoResult != 0 {
      print("Error getting address info: \(errno)")
      return
    }

    let bindResult = bind(socketFD, servinfo!.pointee.ai_addr, socklen_t(servinfo!.pointee.ai_addrlen))

    if bindResult == -1 {
      print("Error binding socket to Address: \(errno)")
      return
    }

    let listenResult = listen(socketFD, //Socket File descriptor
                              8         // The backlog argument defines the maximum length the queue of pending connections may grow to
    )

    if listenResult == -1 {
      print("Error setting our socket to listen")
      return
    }

    while (true) {
      let MTU = 65536
      var addr = sockaddr()
      var addr_len :socklen_t = 0

      print("About to accept")
      let clientFD = accept(socketFD, &addr, &addr_len)
      print("Accepted new client with file descriptor: \(clientFD)")

      if clientFD == -1 {
        print("Error accepting connection")
      }

      var buffer = UnsafeMutableRawPointer.allocate(byteCount: MTU,alignment: MemoryLayout<CChar>.size)

      while(true) {
        let readResult = read(clientFD, &buffer, MTU)

        if (readResult == 0) {
          break;  // end of file
        } else if (readResult == -1) {
          print("Error reading form client\(clientFD) - \(errno)")
          break;  // error
        } else {
          let strResult = withUnsafePointer(to: &buffer) {
            $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: readResult)) {
              String(cString: $0)
            }
          }
          print("Received form client(\(clientFD)): \(strResult)")
          write(clientFD, &buffer, readResult)
        }
      }
    }
  }

}

Right, now we can build and run it:

1
2
3
4
$ swift run
#Welcome to our simple echo server!
#Server starting...
#About to accept

Perfect, our server is now ready to accept connections.

To test it you can use ncat, or rdncat if you have the version we created in the previous post about Network.framework. In a different shell run your favourite version of ncat.

1
$ ncat localhost 1234

You can send text, and you’ll get it back. That’s it. Our server is working correctly. Congratulations!

Final Thoughts

I tried to create the barebones of the server, so we don’t get distracted with anything else, and we focus on using sockets in Swift. We are not going to build the client. I think you’ll be able to piece it together with what we did for the server. The client is more straightforward, but if you want me to write about it, let me know, and I’ll add it.

We also avoided concurrency, no threads and/or GrandCentralDispatch. Again, I did it to keep the server as simple as possible. But I encourage you to explore those topics. They are essential to building a useful server and not only a skeleton as we’ve done today.

Working directly with memory in Swift is not super fun (ok, maybe it can be fun), but it also brings many dangers. If you were observant when you were running your server you might have noticed that the buffer was never cleared. Let me show you what I mean with the following example:

1
2
3
4
5
6
7
8
9
$ swift run
Welcome to our simple echo server!
Server starting...
About to accept
Accepted new client with file descriptor: 7
# the client sends the text "hello"
Received form client(7): hello
# the client sends the text "hi"
Received form client(7): hillo

You see the problem? hi is smaller than hello and the buffer was never cleared, or the string was not null-terminated. That is a simple mistake. Let’s fix it. We can add a null (Character 0) after the bytes we just read to mark the end of the string.

1
2
3
4
5
6
7
          // This is an ugly way to add the null-terminator at the end of the buffer we just read
          // if you know a better way let me know and I'll update the code.
          withUnsafeMutablePointer(to: &buffer) {
                $0.withMemoryRebound(to: UInt8.self, capacity: readResult + 1) {
                    $0.advanced(by: readResult).assign(repeating: 0, count: 1)
                }
          }

You can see how things can start getting messy if we don’t pay attention to what our pointers are doing.

I would also encourage you to have a look at MemoryLayout enumeration, it’s handy to check the size, stride, and alignment of system types. For example, we can get the system size for sockaddr_t structure.

1
print(MemoryLayout<sockaddr_t>.size)

Here is the link to Apple’s documentation to MemoryLayout.

Using sockets directly is not encouraged. It is better to use CFSockets and CFStreams they provide a friendlier interface. But if you need to use sockets, now you know how to use native sockets from Swift. Congratulations!

Ok, that’s it for today. I hope you find the post useful. Let me know what you think. And if you have tips on how to work with sockets send them my way. I’m always looking for better ways to do things.

You can check the full code of the server in the GitHub repository.

Related topics/notes of interest

1
$ lsof -Pan -p PID -i

** There is no comment system yet, but you can send me a message on twitter @rderik or send me an email: derik[at]rderik[dot]com.