rustnetworkingpacketlibcraw-sockets

Extra Bytes Added to Packet When Sending via Raw Socket


Description

I'm currently trying to send packets via a raw socket in Rust. I'm currently using libc to manage the socket. I've had success with sending the packet but the packet sent is never the same as the original one. Here's what's happening, the packet I'm sending is this:

const EXAMPLE: [u8; 44] = [
    0x00, 0x04, 0x00, 0x01, 0x00, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,/* Pay attention here*/ 0x08, 0x06, 
    0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x00, 0xd8, 0x61, 0x57, 0x60, 0x36, 0xc0, 0xa8,
    0x02, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa8, 0x02, 0x01,
];

This is essentially an arp request about 192.168.2.1

The packet being sent is this (according to wireshark)

00 04 00 01 00 06 ff ff ff ff ff ff 00 00 11 00 // Notice the extra 0x0011 here?
08 06 00 01 08 00 06 04 00 01 00 d8 61 57 60 36 
c0 a8 02 08 ff ff ff ff ff ff c0 a8 02 01        

I know what is adding the exta bytes. Here's what I'm doing:

let sock = unsafe { socket(AF_PACKET, SOCK_RAW, 0x3) }; // Allow all protocols
let addr = sockaddr_ll {
  sll_family: 0x3,
  sll_protocol: 0x11,
  sll_ifindex: 2,
  sll_addr: [0; 8],
  sll_halen: 0,
  sll_hatype: 0,
  sll_pkttype: 0,
};

let ret = unsafe { sendto(
  sock.as_raw_fd(),
  EXAMPLE.as_ptr().cast(),
  EXAMPLE.len(),
  0,
  &addr as *const _ as *const sockaddr,
  mem::size_of::<sockaddr_ll>() as u32,
) };
if ret < 0 {
  unreachable!() // No an error
}

After some investigating I've come to the conclusion that the extra bytes belong to sll_protocol.

Possible Cause

I'm not 100% sure but I noticed that sockaddr_ll is 20 bytes long while sockaddr is 16. This leaves 4 bytes. This is extactly the size of out extra bytes. The reason why the extra bytes appear is because I'm casting the pointer type from sockaddr_ll to sockaddr as a result for some reason the 4 extra bytes are inserted in the packet. If this is the case then what's the purpose to mem::size_of::<sockaddr_ll>() it should've been handled correctlu since I provide the size of the address. In addition If my assumtion is correct how do I fix my error?

You can find all the code here: https://github.com/SakPetios/ExtraBytes/tree/main

Expected Result

I'm expecting to see an arp response when sending the packet

Thank you in advance for any assistance with this issue.


Solution

  • Your code basically works for me. You seem to have swapped source and destination MAC in your frame, so your frame was not a broadcast and probably got filtered by the receiver's network card.

    The destination MAC comes first because it allows a switch to quickly make a forwarding decision, before the whole frame has been received (cut-through switching).

    I have changed the payload to a simpler pattern:

    const EXAMPLE: [u8; 44] = [
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xAA, 0xBB, 0x01, 0x02,
        0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
        0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
    ];
    

    I have also changed sll_protocol: 0xCCDD so it will jump out, but I never saw that value in the frame.

    Sniffing on my other PC receiving the frame:

    # tcpdump -i eth1 -n not ip
    08:38:53.644143 01:02:03:04:05:06 > ff:ff:ff:ff:ff:ff, ethertype Unknown (0xaabb), length 60: 
            0x0000:  0102 0304 0506 0708 090a 0b0c 0d0e 0f10  ................
            0x0010:  1112 1314 1516 1718 191a 1b1c 1d1e 0000  ................
            0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............
    

    The Ethertype (the two bytes after the source MAC) matches the data in the buffer. We can also see that padding was added to satisfy the minimum Ethernet frame size.

    So... either your device driver does something funny with sll_protocol, quoting the manpage packet(7):

    SOCK_RAW packets are passed to and from the device driver without any changes in the packet data. [...] That packet is then queued unmodified to the network driver of the interface defined by the destination address. Some device drivers always add other headers.

    Or, more likely, you have been sniffing on the sender with Wireshark, and the frame you looked at is not an Ethernet frame. The Linux kernel doesn't actually receive its own frame back while sending, so what you see is more likely based on the structure that was passed to the driver for sending.

    The Ethernet hardware and driver can do a lot of extra work modifying the frame, like TCP checksum offload or maybe adding a VLAN tag, things that the kernel cannot "read back" for sniffing.


    Other things I noticed in your code: (this doesn't seem to affect the result)

    socket(AF_PACKET, SOCK_RAW, 0x3)
    

    I'm not sure where you got the value 0x3 from, I think it should be zero.

    And sll_family: 3, looks wrong. The manpage packet(7) says:

    struct sockaddr_ll {
        unsigned short sll_family;   /* Always AF_PACKET */
    

    And my headers say:

    #define AF_AX25     3   /* Amateur Radio AX.25      */
    #define AF_PACKET   17  /* Packet family        */
    

    I guess it will get ignored, but still.

    And do yourself a favour and never omit error checking when dealing with a C API. You will not always get sensible follow-up errors.

    let sock = unsafe { socket(AF_PACKET, SOCK_RAW, 0) };
    if sock < 0 {
        eprintln!("socket(): {}", Error::last_os_error());
        return;
    }