This document discusses the PNETSOCK structure and all socket related functionality of libpnet6. See pnet6(3) for an introduction to libpnet6 itself.
- Create and destroy sockets;
- Initiate a connection on a socket;
- Listen for connections on a socket;
- Send and receive data on a socket.
A socket is the basic building block of all network communications. libpnet6 refers to a socket as a PNETSOCK: this is an opaque structure, which contains lots of relevant information about the socket.
libpnet6 sockets are stream-oriented (TCP sockets), datagram oriented (UDP sockets) or raw (for all other protocols, such as for example ICMP, or some user defined protocol that the kernel knows nothing about). This document only discusses TCP and UDP sockets. Raw sockets are discussed further in pnet6-raw(3).
PNETSOCK pnetTCPSocket( void );
PNETSOCK pnetTCPSocket2( int fam );
PNETSOCK pnetUDPSocket( void );
PNETSOCK pnetUDPSocket2( int fam );
PNETSOCK pnetUDPSocketX( int fam );
These functions all create an unconnected PNETSOCK structure and return it. pnetTCPSocket() and pnetUDPSocket() create by default a socket of the IPv6 address family (AF_INET6); pnetTCPSocket2() and pnetUDPSocket2() takes as argument the address family for which (in which?) the socket should be created. The arguments for this function are PNET_IPv4, to create a socket in the AF_INET domain; PNET_IPv6, to create a socket in the AF_INET6 domain; and PNET_LOCAL to create a socket in the AF_UNIX domain (also known as the AF_LOCAL domain). The reason for using the PNET... values is to avoid the need to include numerous, OS dependent header files wherein such constant are normally defined.
The final function, pnetUDPSocketX() can be used when a socket needs to maintain additional information (so-called ancillary or control data) in addition to other ``regular'' things it maintains about a connection. Ancilliary data is often OS specific though, so requesting that a socket should fetch ancilliary data from the kernel does not mean it will actually happen. Access to ancilliary data is described later on in this document.
Regardless of which function is invoked, libpnet6 will first try to open a socket of the requested family; if that fails, it will try the ``other'' family. If both fail, libpnet6 will try again in a few seconds. After (typically) 5 tries, libpnet6 will give up.
All of these function return a valid pointer to a PNETSOCK structure upon success, or a NULL value upon failure. Errors will primarily be out of memory errors, or an unsupported address family for the requested type. Currently, libpnet6 will set an error code, which can be inspected as described in pnet6-api(3).
PNETSOCK pnetTCPConnect( const char* host, const char * serv );
PNETSOCK pnetTCPConnect2( int fam, const char* host, const char * serv );
PNETSOCK pnetTCPConnectX( int fam, const char* host, const char * serv, const char * laddr, const char * lport );
PNETSOCK pnetUDPConnect( const char* host, const char * serv );
PNETSOCK pnetUDPConnect2( int fam, const char* host, const char * serv );
PNETSOCK pnetUDPConnectX( int fam, const char* host, const char * serv, const char * laddr, const char * lport );
int pnetConnect( PNETSOCK ps, const char * host, const char * serv );
int pnetConnectX( PNETSOCK ps, const char * host, const char * serv, const char * laddr, const char * lport );
The first six ``connect'' functions are shortcut functions: they will create a socket, and immediately initiate a connection on it. Once the function returns successfully, it is guaranteed that the connection will have been established. Here too, the only difference between the functions with a 2 in their name and their counterparts is the presence of the address family specification. The same convention holds as for the create functions above.
The functions with an X in their name allow you to set the local address and port for the connection. The address laddr should be an interface name or IP address, while the port lport should be a string containing a number or a service name, such as for example "http". If you don't want to explicitly set the interface or hostname, you can pass a NULL string as laddr, which will let libpnet6 (or actually the kernel) choose the appropriate local interface to use. The socket will be bound to this local address prior to connecting with the remote peer.
NOTE: no actual connection is made for a UDP socket: the only thing that happens is that the remote address is resolved and stored in the socket so that pnetWrite() (discussed later on) knows who to send the data to.
The last two functions are a bit different: they take as argument a valid PNETSOCK structure, and perform a connect on it in the same fashion as the preceeding functions. The reason for these functions is that it might sometimes be necessary to set socket parameters prior to establishing a connection (such as whether the socket should be blocking, or the size of the receive buffer). Setting socket parameters is described further on in this document.
The host parameter can either be a host name, such as "foo.bar.com", or a numeric IP or IPv6 address, such as ``10.2.0.2'' or ``ff01::1''. If an IPv6 address is link local (that is, starting with fe8X, fe9X, feaX or febX, X being any hex character), or multicast with link local scope (starting with ``ffX2:'', again, X being any hex character), then you need to provide the name of the interface on which accesses the given link where the address belongs to. Currently, you can do this by appending the hostname with a '%<ifname>', where <ifname> is either an interface name or an interface index. E.g. ``foo%eth0'' means to resolve host ``foo'' and use interface ``eth0''; or ``ff02::1%4'', which will use interface 4 to reach address ff02::1. You can find out what the interface names and numbers on your system are by using pnetGetIfInfo(), explained in more detail in pnet6-if(3).
All of these function return a valid pointer to a PNETSOCK structure upon success, or a NULL value upon failure. Errors can vary significantly, from out of kernel memory, to a whole plethora of connection related errors. As usual, libpnet6 will set the appropriate error (see pnet6-api(3) ).
int pnetClose( PNETSOCK ps );
int pnetCloseRead( PNETSOCK ps );
int pnetCloseWrite( PNETSOCK ps );
int pnetCloseReadWrite( PNETSOCK ps );
These functions close a socket and return all associated memory to the system. pnetClose() works on any type of socket, of any address family. The rest are only used for sockets that are part of a full-duplex connection. pnetCloseRead() disables the read end of the socket, meaning that no more data can be received on it. Data can still be written however. pnetCloseWrite() closes the write end of the connection; no more data can be sent on the socket. pnetCloseReadWrite() disables both receiving and sending. Note that only pnetClose() actually destroys the socket.
All the Close functions return 0 on success and -1 on failure and an appropriate error code is set.
There are two sides in network communications, the sender and the receiver. The sender will normally need to initiate the connection, while the receiver will need to listen for incoming connections, and handle them as they are initiated.
This section describes how to accept incoming connection.
int pnetListen(PNETSOCK ps, const char * sp);
int pnetListenAt(PNETSOCK ps, const char * ifaddr, const char * sp);
typedef int (*PNETREADCB)(PNETSOCK,void*);
int pnetSockAddReadcallback(PNETSOCK ps, PNETREADCB cb, void * arg);
int pnetStartListen(int mode);
To set a socket up for receiving connections, you need to call either pnetListen() or pnetListenAt(). The first argument is a valid PNETSOCK pointer, created through one of the pnet6 functions described above. The sp argument is either the name of a service such as for example ``ssh'', (see the /etc/services file), or just a port number, such as ``4567''.
pnetListen() will setup the socket ps to accept all incoming connections to this host with a destination port of sp. The type of connection, e.g. TCP or UDP will be deduced from the type of the socket ps, which is specified at socket creation time.
pnetListenAt() takes an argument, ifaddr, which specifies the actual interface or address on which to listen for incoming connections. As such, ifaddr can be in interface name, such as ``rl0'' or ``eth0'', or an address. Note however that, when specifying an address, this can only be the actual address of the interface, or it must be a multicast or broadcast address. So, if interface ``htm0'' has an address 192.168.0.10 and a broadcast address 192.168.0.255, then you can use either the name, the address, the broadcast address, or a multicast address in the call to pnetListenAt(). (This is the same for IPv6 addressing. Also, the same postfix rules apply for link-local addresses, see the explanation under the section Creation with immediate connect). Here again, the type of connection that will be accepted is deduced from the type of the socket.
In order for an application to be able to do anything with a new connection, it should register a read callback with each socket that is listening for connections. This happens through the function pnetSockAddReadcallback(). The arguments are a socket, a PNETREADCB function pointer and a void pointer to any arbitrary data arg. Once a connection is established, each socket will invoke its read callback, passing itself, and the arbitrary data arg as arguments. Inside the read callback you can receive and send data on the socket. See below for the functions that are used for this purpose.
An application can setup one or more sockets for listening in this fashion. The socket can be of different types (TCP/UDP/RAW) and/or address families (IP/IPv6).
Once all required sockets have been setup for listening, the actual listen loop is started by calling pnetListenStart(). The mode argument is only used with STREAM sockets (i.e TCP sockeets only) and can be either PNET_LISTEN_FORK or PNET_LISTEN_THREAD, or PNET_LISTEN_SELF. This indicates to the server how to handle incoming connections: in the first case a child will be forked to handle the new connection; in the second case, a thread will be spawned; and in the third case, the server will handle the connection itself. Note: not all operating systems support all of the above mechanisms. Also, if mode is PNET_LISTEN_THREAD you should be aware that the read callback function for the particular socket will potentially be called by more than one thread at a time. Care should be taken to make sure the read callback function is thread safe. In particular, do not use global or static variables. If you need to use global of static variables, see pnet6-thread(3) on more information about how to make an applicatoin thread-safe in libpnet6.
As usual, the above four functions all return -1 on error and 0 on success.
int pnetWrite(PNETSOCK ps, char * buff, int blen );
int pnetWriteTo(PNETSOCK ps, PNETADDR pa , char * buff, int blen);
int pnetRead(PNETSOCK ps , char * buff, int blen);
int pnetReadBlock(PNETSOCK ps , char * buff, int blen);
int pnetReadTimed(PNETSOCK ps , char * buff, int blen, int block, int msec);
The first two functions send blen bytes of data contained in the buffer buff down the socket ps. pnetWrite() assumes that the socket has already done a pnetConnect(), (either explicitly, or through one of the ``shortcut'' functions described earlier on), which sets the remote address to send data to. If a socket has not called pnetConnect(), then pnetWrite() will fail. In this case, you need to use pnetWriteTo(), spefifying the remote address. Note that for a TCP connection you cannot use pnetWriteTo() without first calling one of the connect functions. If you don't connect the TCP socket, you'll get a EPIPE (broken pipe) error when trying to write data on it.
The last three functions receive a maximum of blen bytes of data into the provided buffer buff (which needs to be at least blen bytes long), from the socket ps. All three can be used on any type of sockets, that is stream, datagram, or raw. pnetRead() will return immediately upon receipt of any data (that is, if there's less bytes available than the number specified by blen pnetRead() will only read that much, and return). If you want libpnet6 to wait untill the requested amount of data has arrived, call pnetReadBlock(): this function will wait until at least blen bytes of data has arrived, before returning. Note that both pnetRead() and pnetReadBlock() might have to wait for ever, should the required amount of bytes never come in. This is a problem, as it might be exploited in a DOS (Denial-of-Service) attack. Therefore, it's better to use the pnetReadTimed() function. Passing a block argument of anything else than zero, causes it to behave like pnetReadBlock(). Passing a block of 0, will cause it to behave like pnetRead(). However, if the last argument is not negative, then pnetReadTimed() will wait only that number of milliseconds, and return, regardless of how much data has arrived. Passing a msec of 0, will cause the function to return immediately, regardless of whether there is input data pending on the socket. A value for msec of less than 0, disables the timer, and makes the function identical to pnetRead() or pnetReadBlock(), (depending on the value of block).
The write functions return the number of bytes written on success and -1 on failure. As a special case, a write function can still generate an error condition, even if some data has been written to the socket (in which case the number of bytes actually sent is still returned to the caller). Therefore, it is advisable to check if the current error is still 0 (signifying that no error occurred) even after a successful call to one of these two functions.
The read functions return the actual bytes of data read from the socket. An exception is pnetReadTimed(): if no data arrives prior to the timer expiring, it will return PNET_READ_TIMED_OUT, else, it will return the number of bytes read. All read functions return PNET_READ_ERROR when an error occurs. As usual, libpnet6 sets the appropriate error code.
The current version of libpnet6 is highly experimental.
You can always get the most recent version from http://pnet6.sourceforge.net.
Peter Bozarov. Send mail to kingofgib (at) users.sourceforge.net