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 10.0.2.1 10.0.2.2 Host C 10.0.3.1 10.0.3.2 10.0.3.1 10.0.3.2 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 Host C 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 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 10.0.2.1 10.0.2.2 Host C 10.0.3.1 10.0.3.2 10.0.3.1 10.0.3.2 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 Host C 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 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 </usr/include/linux/socket.h>. 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) message to server $ ./triptime stargate 54321 $ ./msgserver reply from server round-trip travel-time 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 oval = 1; int 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 struct timeval + { unsigned long unsigned long }; micoseconds tv_sec; tv_usec; This structure-type is defined in the header-file </usr/include/sys/time.h> ‘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 = sizeof( paddr ); mymsghdr.msg_iov = myiov; mymsghdr.msg_iovlen = 1; mymsghdr.msg_control = cbuf; mymsghdr.msg_controllen = sizeof( cbuf ); mymsghdr.msg_flags = 0; int rx = recvmsg( sock, &mymsghdr, 0 ); if ( rx < 0 ) { perror( “recvmsg” ); exit(1); } // 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) // 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 *msgp = &mymsghdr; struct cmsghdr *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?