19

I am trying to send and receive packets of type SOCK_RAW over PF_SOCKETs using my own custom protocol ID on the same machine. Here is my sender and receiver sample code-

sender.c

#include<sys/socket.h>
#include<linux/if_packet.h>
#include<linux/if_ether.h>
#include<linux/if_arp.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define CUSTOM_PROTO 0xB588

int main ()
{
    int sockfd = -1;
    struct sockaddr_ll dest_addr = {0}, src_addr={0};
    char *buffer = NULL;
    struct ethhdr *eh;

    sockfd = socket(PF_PACKET, SOCK_RAW, htons(CUSTOM_PROTO) );

    if ( sockfd == -1 )
    {
        perror("socket");
        return -1;
    }
    buffer = malloc(1518);
    eh = (struct ethhdr *)buffer;

    dest_addr.sll_ifindex  = if_nametoindex("eth0");
    dest_addr.sll_addr[0]  = 0x0;
    dest_addr.sll_addr[1]  = 0xc;
    dest_addr.sll_addr[2]  = 0x29;
    dest_addr.sll_addr[3]  = 0x49;
    dest_addr.sll_addr[4]  = 0x3f;
    dest_addr.sll_addr[5]  = 0x5b;
    dest_addr.sll_addr[6]  = 0x0;
    dest_addr.sll_addr[7]  = 0x0;

    //other host MAC address
    unsigned char dest_mac[6] = {0x0, 0xc, 0x29, 0x49, 0x3f, 0x5b};

    /*set the frame header*/
    memcpy((void*)buffer, (void*)dest_mac, ETH_ALEN);
    memcpy((void*)(buffer+ETH_ALEN), (void*)dest_mac, ETH_ALEN);

    eh->h_proto = htons(PAVAN_PROTO);

    memcpy((void*)(buffer+ETH_ALEN+ETH_ALEN + 2), "Pavan", 6 );

    int send = sendto(sockfd, buffer, 1514, 0, (struct sockaddr*)&dest_addr,
                      sizeof(dest_addr) );
    if ( send == -1 )
    {
        perror("sendto");
        return -1;
    }
    return 0;
}

receiver.c

#include<sys/socket.h>
#include<linux/if_packet.h>
#include<linux/if_ether.h>
#include<linux/if_arp.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define CUSTOM_PROTO 0xB588

int main ()
{
    int sockfd = -1;
    struct sockaddr_ll dest_addr = {0}, src_addr={0};
    char *recvbuf = malloc(1514);

    sockfd = socket(PF_PACKET, SOCK_RAW, htons(CUSTOM_PROTO) );

    if ( sockfd == -1 )
    {
        perror("socket");
        return -1;
    }
    int len = recvfrom(sockfd, recvbuf, 1514, 0, NULL, NULL);
    printf("I received: \n");

    return 0;
}

Both sender and receiver are running on Ubuntu Virtualbox. The problem is the receiver hangs in recvfrom. But in receiver.c, if I change htons(CUSTOM_PROTO) to htons(ETH_P_ALL), the receiver works just fine.

Why is the kernel not delivering the packet with my custom protocol ID to my custom protocol ID socket?

I verified in GDB that the ethernet header is formed correctly when I receive packet with htons(ETH_P_ALL)

Update: Instead of interface eth0 and its corresponding MAC, if I choose local loopback lo and a MAC address of 00:00:00:00:00:00, CUSTOM_PROTO works just fine!

Update 2 CUSTOM_PROTO works fine if the sender and receiver are on different machines. This finding and prev update made me suspect that packets being sent out on eth0 are not being received by the same machine. But the fact that ETH_P_ALL works on the same machine, refutes my suspicion.

Pavan Manjunath
  • 27,404
  • 12
  • 99
  • 125
  • Do you REALLY need a custom transport protocol? Just a cursory search on the web suggests you need to write kernel level code to deal with this. See this question: http://stackoverflow.com/questions/13760017/addition-of-a-new-network-protocol-in-the-linux-kernel – Russ Schultz Nov 02 '15 at 19:03
  • @RussSchultz Actually this is for a course assignment. All of us will be running our code on the same machines as root. We need to use unique protocol IDs to distinguish our packets – Pavan Manjunath Nov 02 '15 at 19:06
  • @RussSchultz In the link you give, they want to handle the new protocol in the kernel and hence all the kernel code. Here, I will do all processing in user space. I dont want the kernel to do anything with my packets except sending packets with my ID to my socket – Pavan Manjunath Nov 02 '15 at 19:08
  • 1
    0xffff is reserved (for whatever purposes). Try 0x88b5 or 0x88b6 which are specifically reserved for private and experimental use. – user58697 Nov 02 '15 at 20:04
  • @user58697 Tried. Even tried 0xB588 and 0xB688 just in case you meant network order. No luck with both. – Pavan Manjunath Nov 02 '15 at 20:09
  • I also presume that `PAVAN_PROTO` is the same as `CUSTOM_PROTO`. – user58697 Nov 02 '15 at 20:16
  • @user58697 Oops. That was a typo. Corrected it. – Pavan Manjunath Nov 02 '15 at 20:17
  • 2
    You tried to use `tcpdump` or other similar tool to check the traffic? There's some rule that could be blocking the packets? – Bruno Nov 08 '15 at 21:07
  • @PavanManjunath Can you post in minimal form we can easily compile and run to verify without screwing around please? – spinkus Nov 09 '15 at 02:58
  • @S.Pinkus Done. Have added compilable code. Just check the MAC address to the one on your machine – Pavan Manjunath Nov 09 '15 at 03:23
  • I am curious what happens if you are not running on VirtualBox and whether the loopback then works properly. there seems to be a few workarounds for some networking applications with VirtualBox https://help.ubuntu.com/community/VirtualBox/Networking however it is all new to me so maybe it is a lack of experience on my part. – Richard Chambers Nov 09 '15 at 03:43
  • @RichardChambers I tried it on VMs with public IPs. Same result. And I need to get this to work on VMs. – Pavan Manjunath Nov 09 '15 at 06:04
  • I understand you need it to work on VirtualBox. I suggest you try it on bare metal first to see if it works or not. If not then implied is there is a problem with your code. If it does work then implied is there is some kind of configuration problem with VirtualBox or it is not supported by VirtualBox. – Richard Chambers Nov 09 '15 at 13:02

2 Answers2

9

ETH_P_ALL vs any other protocol

The protocol ETH_P_ALL has the special role of capturing outgoing packets.

Receiver socket with any protocol that is not equal to ETH_P_ALL receives packets of that protocol that come from the device driver.

Socket with protocol ETH_P_ALL receives all packets before sending outgoing packets to the device driver and all incoming packets that are received from the device driver.

Loopback device vs Ethernet device

Packets sent to the loopback device go out from that device and then the same packets are received from the device as incoming. So, when CUSTOM_PROTO is used with loopback the socket captures packets with custom protocol as incoming.

Note that if ETH_P_ALL is used with the loopback device each packet is received twice. Once it is captured as outgoing and the second time as incoming.

In case of eth0 the packet is transmitted from the device. So, such packets go to the device driver and then they can be seen on the other side of the physical Ethernet port. For example, with VirtualBox "Host-only" network adapter those packets can be captured by some sniffer in the host system.

However, packets transmitted to the physical port (or its emulation) are not redirected back to that port. So, they are not received as incoming from the device. That is why such packets can be captured only by ETH_P_ALL in outgoing direction and they cannot be seen by CUSTOM_PROTO in incoming direction.

Technically it should possible to prepare special setup to do external packet loopback (packets from the device port should be sent back to that port). In that case the behavior should be similar to the loopback device.

Kernel implementation

See the kernel file net/core/dev.c. There are two different lists:

struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
struct list_head ptype_all __read_mostly;   /* Taps */

The list ptype_all is for socket handlers with protocol ETH_P_ALL. The list ptype_base is for handlers with normal protocols.

There is a hook for outgoing packets in xmit_one() called from dev_hard_start_xmit():

    if (!list_empty(&ptype_all))
        dev_queue_xmit_nit(skb, dev);

For outgoing packets the function dev_queue_xmit_nit() is called for ETH_P_ALL processing each item of ptype_all. Finally the sockets of type AF_SOCKET with protocol ETH_P_ALL capture that outgoing packet.


So, the observed behavior is not related to any custom protocol. The same behavior can be observed with ETH_P_IP. In that case the receiver is able to capture all incoming IP packets, however it cannot capture IP packets from sender.c that sends from "eth0" to MAC address of "eth0" device.

It can be also seen by tcpdump. The packets sent by the sender are not captured if tcpdump is called with an option to capture only incoming packets (different versions of tcpdump use different command line argument to enable such filtering).


The initial task where on the same machines it is needed to distinguish packets by protocol IDs can be solved using ETH_P_ALL. The receiver should capture all packets and check the protocol, for example:

while (1) {
    int len = recvfrom(sockfd, recvbuf, 1514, 0, NULL, NULL);

    if (ntohs(*(uint16_t*)(recvbuf + ETH_ALEN + ETH_ALEN)) == CUSTOM_PROTO) {
        printf("I received: \n");
        break;
    }
}

Useful reference "kernel_flow" with a nice diagram http://www.linuxfoundation.org/images/1/1c/Network_data_flow_through_kernel.png

It is based on the 2.6.20 kernel, however in the modern kernels ETH_P_ALL is treated in the same way.

Orest Hera
  • 6,706
  • 2
  • 21
  • 35
  • Thanks for the answer but I dont get you completely. Do you mean that only if the type is ETH_P_ALL does the kernel capture the outgoing packet? What happens to the packet if its any other proto? The packet is supposed to go out of eth0 and come (back) to eth0. Isn't this packet treated as incoming packet? – Pavan Manjunath Nov 11 '15 at 18:41
  • @PavanManjunath Yes, all packets of all protocols in both directions (in and out) are captured by socket with `ETH_P_ALL`. So, in your tests you are able to capture that packet only from outgoing queue. That packet is never received as incoming on the `eth0` device. If the receiving socket is opened with `CUSTOM_PROTO` it can receive only packets that are indeed received through `eth0` from outside. – Orest Hera Nov 11 '15 at 18:51
  • In the above code, I put the `recvfrom` in a while(1) loop and set ETH_P_ALL. I am not receiving 2 packets per sent packet. Did you notice the duplicate packets on the same code I've posted? If not can you please paste your code somewhere on pastebin? (cos answer is very big already :)) – Pavan Manjunath Nov 13 '15 at 23:45
  • @PavanManjunath Duplicate packets only for **loopback** device. In `sender.c`: `dest_addr.sll_ifindex = if_nametoindex("lo");` `unsigned char dest_mac[6] = { 0 };` (`dest_addr.sll_addr[]` should not be set, because it is already zero: `dest_addr = {0}`). – Orest Hera Nov 14 '15 at 00:29
  • You are right! Only the loopback is getting duped packets. I appreciate the thoroughly researched answer. Can you please link up the source files you've mentioned to actual linux kernel code @ free electrons or somewhere? That would allow users to explore more and make your answer more complete – Pavan Manjunath Nov 14 '15 at 01:19
0

When packets with same source nad destination MAC address are transmitted from real network device ethX and physically looped back.

If protocol ETH_P_ALL is specified, packet is captured twice:

  • first packet with socket_address.sll_pkttype is PACKET_OUTGOING
  • and second packet with socket_address.sll_pkttype is PACKET_HOST

If specific protocol is specified CUSTOM_PROTO, packet is captured once:

  • in the case of normal packet: socket_address.sll_pkttypeis PACKET_HOST.
  • in the case of VLAN packet: socket_address.sll_pkttypeis PACKET_OTHERHOST.
Dražen G.
  • 358
  • 3
  • 10