Writing Packets to a Socket

Finally, our example program is ready to send messages to the remote host; it simply writes the data onto the socket:

write(sockfd, mesg, strlen(mesg)+1);

The write( ) system call triggers the write method of the file object associated with the sockfd file descriptor. For socket files, this method is implemented by the sock_write( ) function, which performs the following actions:

1. Determines the address of the socket object embedded in the file's inode.

2. Allocates and initializes a "message header"; namely, a msghdr data structure, which stores various control information.

3. Invokes the sock_sendmsg( ) function, passing to it the addresses of the socket object and the msghdr data structure. In turn, this function performs the following actions:

a. Invokes scm_send( ) to check the contents of the message header and allocate a scm_cookie (socket control message) data structure, storing into it a few fields of control information distilled from the message header.

b. Invokes the sendmsg method of the socket object, passing to it the addresses of the socket object, message header, and scm_cookie data structure.

c. Invokes scm_destroy( ) to release the scm_cookie data structure.

Since the BSD socket has been set up specifying the UDP protocol, the addresses of the socket object's methods are stored in the inet dgram ops table. In particular, the sendmsg method is implemented by the inet_sendmsg( ) function, which extracts the address of the INET socket stored in the BSD socket and invokes the sendmsg method of the INET socket.

Again, since the INET socket has been set up specifying the UDP protocol, the addresses of the sock object's methods are stored in the udp_prot table. In particular, the sendmsg method is implemented by the udp_sendmsg( ) function.

18.2.4.1 Transport layer: the udp_sendmsg( ) function

The udp_sendmsg( ) function receives as parameters the addresses of the sock object and the message header (msghdr data structure), and performs the following actions:

1. Allocates a udpfakehdr data structure, which contains the UDP header of the packet to be sent.

2. Determines the address of the rtable describing the route to the destination host from the dst_cache field of the sock object.

3. Invokes ip_build_xmit( ), passing to it the addresses of all relevant data structures, like the sock object, the UDP header, the rtable object, and the address of a UDP-specific function that constructs the packet to be transmitted.

18.2.4.2 Transport and network layers: the ip_build_xmit( ) function

The ip_build_xmit( ) function is used to transmit an IP datagram. It performs the following actions:

1. Invokes sock_alloc_send_skb( ) to allocate a new socket buffer together with the corresponding socket buffer descriptor (see the earlier section Section 18.1.7).

2. Determines the position inside the socket buffer where the payload shall go (the payload is placed near the end of the socket buffer, so its position depends on the payload size).

3. Writes the IP header on the socket buffer, leaving space for the UDP header.

4. Invokes either udp_getfrag_nosum( ) or udp_getfrag( ) to copy the data of the UDP datagram from the User Mode buffer; the latter function also computes, if required, the checksum of the data and of the UDP header (the UDP standard specifies that this checksum computation be optional). [4]

[4] You might wonder why the IP header is written in the socket buffer before the UDP header. Well, the UDP standard dictates that the checksum, if used, has to be computed on the payload, the UDP header, and the last 12 bytes of the IP header (including the source and destination IP addresses). The simplest way to compute the UDP checksum is thus to write the IP header before the UDP header.

5. Invokes the output method of the dst_entry object, passing to it the address of the socket buffer descriptor.

18.2.4.3 Data link layer: composing the hardware header

The output method of the dst_entry object invokes the function of the data link layer that writes the hardware header (and trailer, if required) of the packet in the buffer.

The output method of the IP's dst_entry object is usually implemented by the ip_output( ) function, which receives as a parameter the address skb of the socket buffer descriptor. In turn, this function essentially performs the following actions:

• Checks whether there is already a suitable hardware header descriptor in the cache by looking at the hh field of the skb->dst destination cache object (see the earlier section Section 18.1.5). If the field is not null, the cache includes the header, so it copies the hardware header into the socket buffer, and then invokes the hh_output method of the hh_cache object.

• Otherwise, if the skb->dst->hh field is null, the header must be prepared from scratch. Thus, the function invokes the output method of the neighbour object pointed to by the neighbour field of skb->dst, which is implemented by the neigh_resolve_output( ) function. To compose the header, the latter function invokes a suitable method of the net_device object relative to the network card device that shall transmit the packet, and then inserts the new hardware header in the cache.

Both the hh_output method of the hh_cache object and the output method of the neighbour object end up invoking the dev_queue_xmit( ) function.

18.2.4.4 Data link layer: enqueueing the socket buffer for transmission

The dev_queue_xmit( ) function takes care of queueing the socket buffer for later transmission. In general, network cards are slow devices, and at any given instant there can be many packets waiting to be transmitted. They are usually processed with a First-In, First-Out policy (hence the queue of packets), even if the Linux kernel offers several sophisticated packet scheduling algorithms to be used in high-performance routers. As a general rule, all network card devices define their own queue of packets waiting to be transmitted. Exceptions are virtual devices like the loopback device (lo) and the devices offered by various tunneling protocols, but we don't discuss these further.

A queue of socket buffers is implemented through a complex Qdisc object. Thanks to this data structure, the packet scheduling functions can efficiently manipulate the queue and quickly select the "best" packet to be sent. However, for the purpose of our simple description, the queue is just a list of socket buffer descriptors.

Essentially, dev_queue_xmit( ) performs the following actions:

1. Checks whether the driver of the network device (whose descriptor is stored in the dev field of the socket buffer descriptor) defines its own queue of packets waiting to be transmitted (the address of the Qdisc object is stored in the qdisc field of the net_device object).

2. Invokes the enqueue method of the corresponding Qdisc object to append the socket buffer to the queue.

3. Invokes the qdisc_run( ) function to ensure that the network device is actively sending the packets in the queue.

The chain of functions executed by the sys_write( ) system call service routine ends here. As you see, the final result consists of a new packet that is appended to the transmit queue of a network card device.

In the next section, we look at how our packet is processed by the network card.

I [email protected] RuBoard

Was this article helpful?

0 0

Post a comment