< Chap 9 TOC: Server Socket | Main | C# Asynchronous Class Example >

 


 

Chapter 9 Part 1:

Server or Listening Sockets Programming

 

 

What do we have in this chapter 9 Part 1?

  1. Intro

  2. Server Creation

  3. Listening Sockets

  4. Accepting Client Connections

  5. Closing the Server

  6. Socket Security

  7. Sharing Ports

  8. Socket Permissions

  9. Set socket permissions

 

 

Intro

 

In the previous chapter, the Socket class was introduced, as well as the basic concepts of setting up a socket and establishing client connections when using a connection-oriented protocol such as TCP. This chapter details how to create a server, or listening socket, with the Socket class that accepts one or more client connections. The previous chapter covered the TcpListener class and illustrated how this process basically works.

This chapter is divided into three sections:

 

  1. Creating server sockets,
  2. Socket security, and
  3. Asynchronous socket operations.

 

The first part discusses how to create a server socket using the Socket class and how to accept a client connection. The second part covers socket security, including how to prevent malicious processes from creating sockets that are bound to the same interface and port as an existing server socket as well as controlling what clients can connect to the server socket. The final section discusses how to use asynchronous operations on the Socket class, which is the building block for high-performance network applications.

 

Server Creation

 

As mentioned earlier, this chapter uses the term server socket to refer to an instance of the Socket class for a connection-oriented protocol used to accept incoming client connections. Once a server is created and set to listen for incoming connections, it can accept only connections - it can’t be used as a client socket. Server sockets have two parts:

 

  1. Creating a listening socket and
  2. Accepting client connections.

 

The next two sections discuss these concepts in detail.

 

Listening Sockets

 

The steps for creating a server socket are similar to client sockets: the socket is created and must be bound followed by calling the Listen() method. A server socket must call the Bind() method to indicate which interface the server will listen on. In general, the server can listen on one specific interface or on all interfaces. For example, if a TCP/IPv4 server is being created on a machine with two network interfaces with the addresses 10.10.10.1 and 192.168.1.1, the server can be bound specifically to one of these addresses, such as the 10.10.10.1 address and port 5150. If a client TCP connection request is received on the 192.168.1.1 interface and port 5150, it will be rejected because no server is listening on that address and port. If the server wants to receive connections across all interfaces, it should bind to the wildcard address for the address family being used, which for IPv4 is IPAddress.Any (equal to 0.0.0.0).

Once the socket is bound, the call to Listen() indicates that the socket will be a server socket that accepts client connections received on the interface and port the socket was bound to. Note that Listen() can be called only on sockets for protocols that are connection-oriented (such as TCP). If Listen() is called on a non-connection-oriented protocol, a SocketException is generated. Also, if Listen() is called on a Socket object that’s not bound, a SocketException is thrown.

The netstat.exe command-line utility can be used to display TCP and UDP sockets and what addresses and ports they are bound to, as well as indicating the state of the socket (connected, listening, and so on).

 

The nestat tool run from the Windows XP SP 2 command line

 

The following code is the output of netstat -aon. On Microsoft Windows XP and later, the -o switch will include the process ID of the process owning the given socket entry.

 

A sample of the netstat tool run with -aon argument from the command line

 

The following code illustrates creating a TCP/IPv4 server socket listening on the wildcard address:

 

C#

 

IPEndPoint listenEndPoint = new IPEndPoint( IPAddress.Any, 5150 );

Socket tcpServer = new Socket(

    listenEndPoint.AddressFamily,

    SocketType.Stream,

    ProtocolType.Tcp

    );

tcpServer.Bind( listenEndPoint );

tcpServer.Listen( int.MaxValue );

 

Visual Basic .NET

 

Dim listenEndPoint As IPEndPoint = New IPEndPoint(IPAddress.Any, 5150)

Dim tcpServer As Socket = New Socket( _

    listenEndPoint.AddressFamily, _

    SocketType.Stream, _

    ProtocolType.Tcp _

    )

tcpServer.Bind(listenEndPoint)

tcpServer.Listen(Integer.MaxValue)

 

The only parameter to Listen() is known as the backlog, which indicates how many client TCP connections can be queued on the listening socket at any given time. A server socket can dequeue a client connection by calling the Accept() method or by using the asynchronous BeginAccept(). If the backlog is set to 1, it means that if the server does not accept the client connection, only a single TCP client connection will be accepted. Any subsequent connection requests will be refused by the networking stack. Once the server accepts the connection, the pending connection is removed from the queue and another connection can be queued. The client that’s refused will receive a SocketException on its connection request.

In the previous code sample, the backlog is set to the maximum integer value, but note that the underlying network stack will reset this value to its internal maximum value. Currently, on Windows Server, the maximum is 200. On professional and home versions of Windows, the maximum backlog is 5. If a server is expected to handle a large number of incoming connections, the backlog should be set to the maximum. On Windows Server 2003 SP1 and later, the home and client versions will have the same backlog limit as server versions.

 

Accepting Client Connections

 

After the server socket has been established, the server can make a blocking call to Accept() to accept a client connection. If a client connection is pending, it’s dequeued from the listen backlog, and if no connection is pending, the Accept() call blocks until a client connection request is received. The asynchronous call to accept a connection is BeginAccept() and will be covered in detail in the “Asynchronous Accept” section later in this chapter.

The Accept() method takes no arguments and simply returns a Socket object that references the client connection. All data transfers then occur on the Socket object returned. The RemoteEndPoint property of the returned Socket object contains the address information of the connecting client.

The following code continues the previous example. In this code, once the listening socket is established, the server sits in an infinite loop waiting for a client connection. Once a client connection is accepted, data is received from the client until the connection is closed, at which point the server will handle the next client connection.

 

C#

 

Socket  tcpClient;

byte [ ] receiveBuffer = new byte [ 4096 ];

 

while ( true )

{

    try

    {

        tcpClient = tcpServer.Accept();

        Console.WriteLine("Accepted connection from: {0}",

            tcpClient.RemoteEndPoint.ToString());

        try

        {

            while ( true )

            {

                int rc = tcpClient.Receive( receiveBuffer );

                if ( rc == 0 )

                    break;

            }

        }

        catch ( SocketException err )

        {

            Console.WriteLine("Error occurred on accepted socket: {0}",

                err.Message);

        }

        finally

        {

            tcpClient.Close();

        }

    }

    catch ( SocketException err )

    {

        Console.WriteLine("Accept failed: {0}", err.Message);

    }

}

 

Visual Basic .NET

 

Dim tcpClient As Socket

Dim receiveBuffer(4096) As Byte

Dim rc As Integer

 

While (True)

    Try

        tcpClient = tcpServer.Accept()

        Console.WriteLine("Accepted connection from: {0}", _

        tcpClient.RemoteEndPoint.ToString())

        Try

            While (True)

                rc = tcpClient.Receive(receiveBuffer)

                If (rc = 0) Then

                    Exit While

                End If

            End While

        Catch err As SocketException

            Console.WriteLine("Error occurred on accepted socket: {0}", _

                err.Message)

        Finally

            tcpClient.Close()

        End Try

    Catch err As SocketException

        Console.WriteLine("Accept failed: {0}", err.Message)

    End Try

End While

 

Take a look at the SimpleSocket sample in Chapter 8 for an example on setting up a blocking TCP server.

 

 

Closing the Server

 

When a server no longer wants to accept client connections, it simply needs to call the Close() method on the listening socket. Any queued client connection requests that have not been accepted with a blocking or an asynchronous accept will be reset. All client connections already accepted will not be affected when the server socket closes because each client is its own network connection that will not be closed until the Close() method is called on its Socket object.

 

Socket Security

 

There are many aspects to adding security to sockets, ranging from preventing malicious local processes from stealing ports to authenticating client connections to detecting a denial of service attack. In this chapter, we focus mainly on the socket sharing issue, but we’ll also discuss the SocketPermission class, which can be used to override the default socket security policy enforced by the Microsoft Windows .NET Framework. Chapter 14 will discuss how to detect malicious clients by managing server resources and limiting how long a client can be idle.

 

Sharing Ports

 

Under certain circumstances, it’s possible to have several sockets bound to the same address and port. However, depending on the protocol, the effects can be undesirable. For TCP and UDP, it’s undetermined which socket will receive traffic. The only case where it’s truly useful to have several sockets bound to the same address and port is for IP multicasting, where each socket bound to the same local interface and port and joined to the same multicast group will each receive a copy of the data. Normally, if an attempt is made to bind a socket to an interface and port already in use, a SocketException occurs.

For all versions of Windows prior to Windows Server 2003, sockets were shareable by default. On a pre-Windows Server 2003 machine, if a second socket is created and SocketOptionName.ReuseAddress is set via SetSocketOption, the socket can be bound to an interface and a port already being used by another socket. To prevent another socket from doing this, a new socket option, SocketOptionName.ExclusiveAddressUse, was introduced on Windows NT 4 Service Pack 4 and later (but not on Windows 9x). If a socket sets this option before binding, then after the socket is bound, no other sockets can reuse the same address and port regardless of whether the ReuseAddress option is set.

On Windows Server 2003 and later, the default security model for sockets was changed so that sockets are not shareable. As a result of this change, the uses of the two socket options, ReuseAddress and ExclusiveAddressUse, have changed somewhat. Now if a socket wants to allow others to bind to the same address and port, the first socket must set the ReuseAddress option, which is in effect setting the permission to allow others to steal the address and port. Then the second socket must also set the ReuseAddress option to bind to the same address and port.

There is one catch, however, when binding a socket to the wildcard address under the new security scheme. Even though the socket is not shareable, it’s not exclusive when it comes to the wildcard address. Therefore, the first socket can bind to the wildcard address and specific port, but a second socket can come along and bind to an explicit address on the same port. For example, on a computer with two interfaces, 10.10.10.1 and 10.10.10.2, the first socket can bind to 0.0.0.0:5150, and a second socket can bind to 10.10.10.1:5150. In this case, all traffic received on 10.10.10.1:5150 will be handled by the second socket, and all traffic received on port 5150 for interfaces other than 10.10.10.1 will be handled by the first socket.

As you can see, in the case where the socket is bound to the wildcard address on Windows Server 2003 and later, another socket can’t bind to the wildcard address and same port (or a SocketException will be thrown), but it can bind to an explicit interface and port. However, if the server socket sets ExclusiveAddressUse, even binding to the explicit interface will fail. In general, a server should always set the ExclusiveAddressUse option regardless of which operating system it’s running on. The only exception is for multicast UDP sockets.

 

Socket Permissions

 

As we saw in Chapter 8, different levels of code access security are available for socket applications depending on the zone in which the application is run. Under certain circumstances, an application can invoke untrusted assemblies or another application that resides in a more restrictive zone. In this case, the executed code is subjected to the code access permissions, which can result in socket operations failing. However, the SocketPermission class can be used to grant access to socket resources for subsequently invoked socket code.

The SocketPermission class enables applications to specify the resources to which subsequent socket calls have access to. For example, the permissions set could limit code to creating TCP sockets that connect to a single destination. Any attempt to connect to a different destination will fail. The permission properties that are used to grant or deny access are listed in Table 9-1.

 

Table 9-1: Socket Permission Properties

 

Property

Description

Transport type

Defines the transport on which to control. The transport value can be all, connection-oriented, connectionless, or the specific transports (TCP or UDP).

Host name/address

Indicates the host name or string address being granted access to.

Port number

Indicates the port number being granted access to, which can either be a specific port or all ports.

 

Set socket permissions

 

1.      Create an instance of the SocketPermission class. There are two constructors: one which sets the initial permission state (to either unrestricted or none) and one that allows a specific transport, host name, and port to allow access to. The following code shows how to create a SocketPermission instance with no permissions:

 

C#

 

SocketPermission sockPerm1;

 

sockPerm1 = new SocketPermission(System.Security.Permissions.PermissionState.None);

 

Visual Basic .NET

 

Dim sockPerm1 As SocketPermission

 

sockPerm1 = New SocketPermission(System.Security.Permissions.PermissionState.None)

 

2.      Once the initial SocketPermission object is created, individual permissions can be added to the object. The following code allows sockets to connect to any port on the host "AllowedHost" using the TCP transport:

 

C#

 

sockPerm1.AddPermission(

    NetworkAccess.Connect,

    TransportType.Tcp,

    "AllowedHost",

    SocketPermission.AllPorts

    );

 

Visual Basic .NET

 

sockPerm1.AddPermission( _

    NetworkAccess.Connect, _

    TransportType.Tcp, _

    "AllowedHost", _

    SocketPermission.AllPorts _

    )

 

Note that the SocketPermission constructor can be used to specify a single allowed host from step 2.

 

C#

 

sockPerm1 = new SocketPermission(

    NetworkAccess.Connect,

    TransportType.Tcp,

    "AllowedHost",

    SocketPermission.AllPorts

    );

 

Visual Basic .NET

 

sockPerm1 = New SocketPermission( _

    NetworkAccess.Connect, _

    TransportType.Tcp, _

    "AllowedHost", _

    SocketPermission.AllPorts _

    )

 

3.      Enforce the socket permission for all subsequent socket calls by calling the Demand() method.

 

C#

 

sockPerm1.Demand();

 

Visual Basic .NET

 

sockPerm1.Demand()

 

4.      The Deny() method will prevent any function higher in the call stack from modifying the socket permissions granted. Once the desired permissions are established and demanded, the Deny() method should be invoked before calling the untrusted code.

 

C#

 

sockPerm1.Deny();

InvokeUntrustedSocketCode();

 

Visual Basic .NET

 

sockPerm1.Deny()

InvokeUntrustedSocketCode()

 

If multiple SocketPermission classes are created, the Union() and Intersect() methods can be used to perform logical operations on sets of permissions. These methods return a new SocketPermission instance containing the results of the operation.

Finally, note that the permissions granted by the SocketPermission class are applied only when the executed code does not have socket permissions. For example, creating and demanding a SocketPermission that allows a connection to a single host will not be enforced when run in the local computer zone because socket operations are unrestricted in that zone. In other words, the SocketPermission class can’t deny access to socket resources if the executed code has permission to access them.

 

 

 


 

< Chap 9 TOC: Server Socket | Main | C# Asynchronous Class Example >