views:

559

answers:

3

What is the right (portable, stable) way to get the ToS byte of a received packet? I'm doing UDP with recvmsg() and on linux I can get the ToS if I setsockopt() IP_RECVTOS/IPV6_RECVTCLASS, but IP_RECVTOS doesn't seem to be available on my BSD systems. What is the right way to do this?

I primarily want this to work on the BSDs and Solaris.

Edit: To clarify: I currently use recvmsg() where I get the TTL and TOS in the msg_control field on Linux, but in order to get TTL and TOS I need to setsockopt()-enable IP_RECVTTL and IP_RECVTOS. And since Solaris and BSD (working with FreeBSD at the moment) don't have IP_RECVTOS from what I can see I don't get TOS when looping over the CMSG data.

I tried enabling IP_RECVOPTS and IP_RECVRETOPTS, but I still don't get any IP_TOS type CMSG.

Edit 2: I want ToS to be able to verify (as much as possible) that it wasn't overwritten in transit. If for example a VoIP app all of a sudden notices that it's not getting EF tagged packets, then something is wrong and there should be an alarm. (and no, I'm not expecting EF to be respected or preserved over the public internet)

I want TTL basically just because I can. Hypothetically this could be used to trigger "something changed in the network between me and the other side" alerts, which can be useful to know if somethings stops working at the same time.

A: 

Unfortuneatly this sort of thing usually varies across different *ixs. On Solaris you want to use getsockopt with IP_TOS; I don't know about BSD.

See man 7 ip for details.

Dave Rigby
But isn't IP\_TOS just used to get and set (with getsockopt() and setsockopt()) the TOS for the *outgoing* TOS. Are you saying I can use it on a packet by packet bases to get *incoming* TOS bytes?
Thomas
A: 

The "proper" and standard solution is probably to use cmsg(3). You'll find a complete description in Stevens' "Unix network programming" book, a must-read.

Google Code Search found me this example of use.

bortzmeyer
That's what I'm doing. But I only get cmsgs for what I have set up setsockopt for. This is the way I get the TTL, by setting IP_RECVTTL and then getting it in my msg_control from recvmsg(), but only linux seems to even *have* IP_RECVTOS, which seems to be required for the kernel to send ToS info in cmsg.
Thomas
From the zorp changelog: lib/packsock.c: fix Solaris compilation by enabling IP_RECVTOS only if ZORPLIB_ENABLE_TOS is defined.Implying that it won't work on solaris.
Thomas
+2  A: 

I was thinking if you can create two sockets.

  1. One socket of type DGRAM used exclusively for sending

  2. One Raw socket used exclusively for receiving.

Since you are using UDP, you can call a bind + recvFrom on the Raw Sock Fd and then manually unpack the IP header to determine the TOS or TTL.

When you want to send, use the DGRAM sockFd so you dont have to bother to actually create the UDP & IP packet yourself.

There may be issues like the kernel may pass the received buffer to both sockets or to the UDP socket instead of Raw socket or just to the Raw socket. If that is the case (or if it is implementation dependent) then we are back to square one. However, you can try calling bind on the Raw socket and see if it helps. I am aware this maybe a hack but searching on the net for a setsockopt for BSD returned nothing.

EDIT: I wrote a sample program It kind of achieves the objective.

The code below creates two sockets (one raw & one udp). The udp socket is bound on the actual port I am expecting to receive data whereas the raw socket is bound on Port 0. I tested this on Linux and like I expected any data for port 2905 is received by both the sockets. I am however able to retrieve the TTL & TOS values. Dont downvote for the quality of the code. I am just experimenting whether it will work.

Further EDIT: Disabled the receive by UDP socket. I have further enhanced the code to disable the receive by the UDP packet. Using setsockopt, I set the UDP's socket receive buffer to 0. This ensures the kernel does not pass the packet to the UDP socket. IMHO,You can now use the UDP socket exclusively for sending and the raw socket for reading. This should work for you in BSD and Solaris also.

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<arpa/inet.h>
#include<string.h>

#include "protHeaders.x"
#include "gen.h"

 int main(void)
 {
  S32 rawSockFd;
  S32 udpSockFd;
  struct sockaddr_in rsin;
  struct sockaddr_in usin;
  S32 one = 1;
  const S32* val = &one;
  struct timeval tv;
  fd_set rfds;
  S32 maxFd;
  S16 ret;
  S8 rawBuffer[2048];
  S8 udpBuffer[2048];
  struct sockaddr udpFrom,rawFrom;
  socklen_t rLen,uLen;

  memset(rawBuffer,0,sizeof(rawBuffer));
  memset(udpBuffer,0,sizeof(udpBuffer));
  memset(udpFrom,0,sizeof(udpFrom));
  memset(rawFrom,0,sizeof(rawFrom));

  if ((rawSockFd = socket(PF_INET,SOCK_RAW,IPPROTO_UDP)) < 0)
      {
         perror("socket:create");
         RETVALUE(RFAILED);
      }

  /* doing the IP_HDRINCL call */
  if (setsockopt(rawSockFd,IPPROTO_IP,IP_HDRINCL,val,sizeof(one)) < 0)
    {
       perror("Server:setsockopt");
       RETVALUE(RFAILED);
    }

   rsin.sin_family      = AF_INET;
   rsin.sin_addr.s_addr = htonl(INADDR_ANY);
   rsin.sin_port        = htons(0);

   usin.sin_family      = AF_INET;
   usin.sin_addr.s_addr = htons(INADDR_ANY);
   usin.sin_port        = htons(2905);

   if(bind(rawSockFd,(struct sockaddr *)&rsin, sizeof(rsin)) < 0 )
    {
     perror("Server: bind failed");
     RETVALUE(RFAILED);
    } 


  if ((udpSockFd = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0)
      {
         perror("socket:create");
         RETVALUE(RFAILED);
      }

   if(bind(udpSockFd,(struct sockaddr *)&usin, sizeof(usin)) < 0 )
    {
     perror("Server: bind failed on udpsocket");
     RETVALUE(RFAILED);
    } 

  /*set upd socket receive buffer to 0 */
 one = 0; 
 if (setsockopt(udpSockFd,SOL_SOCKET,SO_RCVBUF,(char *)&one,sizeof(one)) < 0)
  {
     perror("Server:setsockopt on udpsocket failed");
     RETVALUE(RFAILED);
  }

  tv.tv_sec  = 0;
  tv.tv_usec = 0;

  maxFd = (rawSockFd > udpSockFd)? rawSockFd:udpSockFd;

    while(1)
    {
     FD_ZERO(&rfds);
     FD_SET(rawSockFd,&rfds);
     FD_SET(udpSockFd,&rfds);

     ret = select(maxFd+1,&rfds,0,0,&tv);

      if ( ret == -1)
         {
             perror("Select Failed");
             RETVALUE(RFAILED);
         }

       if(FD_ISSET(rawSockFd,&rfds))
        {
          printf("Raw Socked Received Message\n");
          if(recvfrom(rawSockFd,rawBuffer,sizeof(rawBuffer),0,&rawFrom,&rLen) == -1)
           {
              perror("Raw socket recvfrom failed");
              RETVALUE(RFAILED);
           }
         /*print the tos */
          printf("TOS:%x\n",*(rawBuffer+1));
          printf("TTL:%x\n",*(rawBuffer+8));
        }

       if(FD_ISSET(udpSockFd,&rfds))
        {
          printf("UDP Socked Received Message\n");
          if(recvfrom(udpSockFd,udpBuffer,sizeof(udpBuffer),0,&udpFrom,&uLen) == -1)
           {
             perror("Udp socket recvfrom failed");
             RETVALUE(RFAILED);
           }
          printf("%s\n",udpBuffer);
        }


    }

  RETVALUE(ROK);
 }
Aditya Sehgal
Hmm... will that work though? I would have to bind() both sockets to the same address:port.
Thomas
Its worth a try especially. Let me try writing some code and see how it spans out.
Aditya Sehgal
Aditya Sehgal
+1 for providing code.
eed3si9n
Awesome Aditya! The only downside I see is that it requires root privileges for the raw socket, but for OSs that don't have IP_RECVTOS this is probably the best answer. I will give it a try.
Thomas
Thomas, another thing. With a raw socket on port 0, you will receive all UDP data for all ports destined to your node. When you do a recvfrom, you can either look at the "from" address to determine whether it is for your application (inefficient) or you can call *connect* with the IP:port of the remote side that you are expecting to send/receive data to. In case there are multiple *remote* sides, you can call *connect* multiple times too. Thanks for the appreciation.
Aditya Sehgal