lesson7

advertisement
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?
Download