Transcript On receiving ancillary data We use the ‘recvmsg()’ function to
On receiving ancillary data
We use the ‘recvmsg()’ function to obtain a datagram’s arrival-time as a UDP socket option
The ‘traceroute’ algorithm
• The idea is to send out a succession of packets to an internet destination, with increasing values for the IP header’s ‘Time-to-Live’ field, knowing that whenever any packet arrives at a router, its ‘Time-to-Live’ field will get decremented • Routers discard packets whose TTL is zero, and return an ICMP error message ‘Time Exceeded’ to the sender which shows that router’s identity • Then this routing information can be displayed
Algorithm implementation
• Probably the most natural way to think about implementing this ‘traceroute’ idea would be to send ICMP ‘Echo Requests’ • When the ‘Time-to-Live field is sufficiently large, the ICMP Echo Request will arrive at its intended target, and an ‘Echo Reply’ ICMP message would get sent back, as with the well known ‘
ping
’ network utility
Sending ICMP packets
Echo Request to D (TTL=1) Host A 10.0.1.1
10.0.1.2
Host B 10.0.2.1
10.0.2.2
Host C 10.0.3.1
10.0.3.2
Host D Time Exceeded to A Echo Request to D (TTL=2) Host A 10.0.1.1
10.0.1.2
Host B 10.0.2.1
10.0.2.2
Host C 10.0.3.1
10.0.3.2
Host D Time Exceeded to A Echo Request to D (TTL=3) Host A 10.0.1.1
10.0.1.2
Host B 10.0.2.1
10.0.2.2
Host C 10.0.3.1
10.0.3.2
Host D Echo Reply to A
Need a RAW socket
• For a Linux application to send an ICMP message, it has to open a type of socket which normally requires ‘root’ privileges • You can look at our ‘
nicwatch
’ utility for an example that uses a RAW socket to access the fields in a packet’s headers • Without ‘root’ privileges we can’t write a ‘traceroute’ utility that is based on ICMP
The Linux ‘workaround’
• What we can do instead is to send UDP datagrams which have increasing values for the TTL-field in their IP-headers (since the TTL-field for any outgoing packets is something that an unprivileged application program can control with a ‘socket option’) • We can detect the identity of routers that send us ‘Time Exceeded’ messages by looking at ‘ancillary data’ via ‘
recvmsg
()’
Sending UDP packets
UDP message to D (TTL=1) Host A 10.0.1.1
10.0.1.2
Host B 10.0.2.1
10.0.2.2
Host C 10.0.3.1
10.0.3.2
Host D Time Exceeded to A UDP message to D (TTL=2) Host A 10.0.1.1
10.0.1.2
Host B 10.0.2.1
10.0.2.2
Host C 10.0.3.1
10.0.3.2
Host D Time Exceeded to A UDP message to D (TTL=3) Host A 10.0.1.1
10.0.1.2
Host B 10.0.2.1
10.0.2.2
Host C 10.0.3.1
10.0.3.2
Host D Destinnation Unreachable to A
Using an unlikely port?
• The most common way of getting back an ‘Destination Unreachable’ error-message (ICMP Type 3) is by sending a datagram to a UDP port which is not being used by any applications currently running on the destination host (ICMP Type 3, Code 3: Port is unreachable) – but a sender might not possess that knowledge for certain, thus a ‘best guess’ approach can be tried
Other UDP senarios…
• We might never get any response from a destination host – e.g., our datagram DID arrive at its destination successfully, and thus ‘
sendmsg
()’ times out while waiting • Or our destination host lies behind some ‘firewall’ that has been erected along the path, or a intermediate router could be configured to ‘block’ an ICMP response
Your project #1
• Your first programming challenge is to try implementing your own miniature version of the ‘traceroute’ utility, devising your own way of handling the less common senarios • The main requirement will be to display the IP-addresses for the various routers encountered along a path to your target • Enhance that basic capability: extra credit
Message-header
struct msghdr { void socklen_t struct iovec int void int int }; *msg_name; msg_namelen; *msg_iov; msg_iovlen; *msg_control; msg_controllen; flags; // optional address // size of address // scatter/gather array // no. of members // ancillary data buffer // ancillary buffer length // flags on received message struct iovec { void *iov_base; size_t iov_len; }
Ancillary control-data
The ancillary data that is returned by ‘sendmsg()’ in the buffer pointed to by its message header’s ‘
cmsg_control
’ field is delivered in a succession of one or more packages, each beginning with this ‘struct cmsghdr’ format, whose data may be followed by padding to achieve a required alignment. struct cmsghdr { socklen_t int int unsigned char }; cmsg_len; cmsg_level; cmsg_type; cmsg_data[0]; // data byte count, including header // originating protocol’s ID-number // protocol-specific type ID-number // variable amount of data follows To avoid the compiler generating code that would be architecture-dependent, these packages of ancillary data should be accessed using special macros named CMSG_FIRSTHDR(), CMSG_DATA(), CMSG_NXTHDR(), etc., and defined for Linux systems in the header-file .
Demo: ‘triptime.cpp’
• We illustrate the role of socket options and access to ancillary data with this example, which sends a datagram to a host that is running our ‘msgserver’ echo-server, and uses a ‘timestamp’ that our socket binds to the returned datagram to get the packet’s round-trip travel-time (in microseconds)
$ ./triptime stargate 54321 message to server reply from server
round-trip travel-time
$ ./msgserver
stargate
The SO_TIMESTAMP option
• When this socket-layer option is enabled, and a suitably sized buffer is provided for the specified ancillary data, the network protocol software reports the arrival-time of the datagram messages it receives • Comparing arrival-time to departure-time allows calculating a ‘round-trip’ travel-time
‘setsockopt()’
• Here are the code-fragments used to open a datagram socket, and then to enable its ability to ‘timestamp’ any arriving packets int sock =
socket
( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); if ( sock < 0 ) { perror( “socket” ); exit(1); } int int oval = 1; olen = sizeof( oval ); if ( setsockopt( sock, SOL_SOCKET, SO_TIMESTAMP, &oval, olen ) < 0 ) { perror( “setsockopt TIMESTAMP” ); exit(1); }
‘struct timeval’
• Linux systems support a data-structure that allows ‘time-of-day’ to be accurately recorded (in seconds and microseconds) seconds micoseconds
+
struct timeval { unsigned long unsigned long }; tv_sec; tv_usec;
This structure-type is defined in the header-file
‘gettimeofday()’
• This library-function records the current time-of day in a ‘struct timeval’ record # declare a ‘struct timeval’ object struct timeval now; # store the current time based on the system’s clock
gettimeofday
( &now, NULL ); • Our ‘triptime.cpp’ example uses this to get the departure-time of an outgoing packet
The ‘cbuf’ array
• The arrival-time for a received packet may be obtained using the ‘recvmsg()’ function with a suitably initialized message-header • For the case of receiving timestamp data, the ‘struct cmsg’ buffer will need room for a ‘struct timeval’ object as its ‘cmsg_data’, in addition to its ‘struct cmsghdr’ header • We need to know sizes for C data-types
Types for x86_64 Linux
• On our x86_64 Linux platform, the sizes for ‘int’ and ‘long’ are 32-bits and 64-bits, respectively, so we’ll need buffer-space that can store 16 (=8+4+4) bytes for the ‘struct cmsghdr’ header, plus 16 (=8+8) bytes for the ‘struct timeval’ data-object • (Our demo actually allocates 40 bytes)
Initializing ‘mymsghdr’
• Here are code-fragments that prepare our ‘struct msghdr’ for receiving the timestamp unsigned char struct iovec buf[ 40 ] = { 0 }, cbuf[ 40 ] = { 0 }; myiov[1] = { { buf, 40 } }; struct msghdr mymsghdr; mymsghdr.msg_name
= &paddr; mymsghdr.msg_namelen
mymsghdr.msg_iov
mymsghdr.msg_iovlen
mymsghdr.msg_control
= sizeof( paddr ); = myiov; = 1; = cbuf; mymsghdr.msg_controllen
mymsghdr.msg_flags
= sizeof( cbuf ); = 0; // server socket-address // socket-address length // address of I/O-vector // elements in I/O-vector // address for control data // control data buffer size // flag settings (none) int rx = recvmsg( sock, &mymsghdr, 0 ); if ( rx < 0 ) { perror( “recvmsg” ); exit(1); } // receive the datagram
Using the macros
• Here is how we use the system’s macros to get the ‘timestamp’ on the arriving data struct timeval int tvrecv; tlen = sizeof( struct timeval ); struct msghdr struct cmsghdr *msgp = &mymsghdr; *cmsg; for ( cmsg = CMSG_FIRSTHDR( msgp ); cmsg != NULL; cmsg = CMSG_NXTHDR( msgp, cmsg ) ) { if (( cmsg->cmsg_level == SOL_SOCKET ) &&( cmsg->cmsg_type == SO_TIMESTAMP )) memcpy( &tvrecv, CMSG_DATA( cmsg ), tlen ); }
Computing RTT
• The Round-Trip travel-time can be gotten by a subtraction (arrival minus departure) provided we convert each ‘struct timeval’ record into a single numerical value that expresses these times in microseconds # convert structures to numbers (one-million microseconds per second) unsigned long long stamp0 = tvsend.tv_sec * 1000000LL + tvsend.tv_usec; unsigned long long stamp1 = tvrecv.tv_sec * 1000000LL + tvrecv.tv_usec; # subtract departure-time from arrival time to get the ‘round-trip’ travel-time unsigned long long rtt = stamp1 – stamp0;
Observation
• Our ‘msgserver.cpp’ echo-server program performs a certain amount of processing between the time it receives a datagram and the time it sends back its response: – It displays a report about the packet’s size – It displays the text of the packet’s message – It modifies a member of its I/O-vector array • These steps take some time – which gets included in the client’s round-trip measure
In-class exercises
• Could you reduce the size of the ‘cbuf[40]’ array in our ‘triptime.cpp’ application, since it appears the timestamp and its cmsghdr could fit in fewer than 40 bytes of storage? • Could you shrink the round-trip travel-time by a noticeable number of microseconds if you omitted the server’s system-calls that write information to the screen-display?