Search code examples
c++linuxboost-asioraw-sockets

Layer 2 header is not added by OS (Linux) when using boost::asio::generic::raw_protocol::socket


I am using boost::asio::generic::raw_protocol::socket to open a socket of family PF_PACKET and type SOCK_DGRAM.

In my header file:

class MyClass
{
public:
MyClass(/*parameters*/);
typedef boost::asio::generic::raw_protocol raw_protocol_t;
typedef boost::asio::generic::basic_endpoint<raw_protocol_t> raw_endpoint_t;

void SetDestinationEndPoint(int ifIndex, int protocol, const uint8_t *destinationMacAddress);

private:
boost::shared_ptr<raw_protocol_t::socket> m_socketPtr;
raw_endpoint_t *m_pDestinationEndpoint;
};

In the .cpp file:

MyClass::MyClass(/*parameters*/):
m_socketPtr(new raw_protocol_t::socket(IOWorker.get_service()) //in my class constructor
{
//constructor body
m_socketPtr->open(raw_protocol_t(PF_PACKET, SOCK_DGRAM));

sockaddr_ll sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sll_family = AF_PACKET;
sockaddr.sll_protocol = htons(protocol);
sockaddr.sll_ifindex = ifIndex;

m_socketPtr->bind(raw_endpoint_t(&sockaddr, sizeof(sockaddr)));
}
//then User sets destination endpoint
void MyClass::SetDestinationEndPoint(int ifIndex, int protocol, const uint8_t *destinationMacAddress)
{
struct sockaddr_ll ll;
memset(&ll, 0, sizeof(ll));
ll.sll_family = AF_PACKET;
ll.sll_ifindex = ifIndex;
ll.sll_protocol = htons(protocol);
ll.sll_halen = ETH_ALEN;
memcpy(ll.sll_addr, destinationMacAddress, ETH_ALEN);
m_pDestinationEndpoint = new raw_endpoint_t(&ll, sizeof(ll), protocol);
}

Finally, a call to async_send_to is made to initiate sending of the payload:

m_socketPtr->async_send_to(boost::asio::buffer(bufferPtr->GetBuffer().get(), bufferPtr->GetBufferSize()),
*m_pDestinationEndpoint,
boost::bind(/*params*/));

The buffer contains only the Ethernet payload and does not include the Ethernet header. As per my understanding, everything required to construct the Ethernet header is present in *m_pDestinationEndpoint (Set using the call to SetDestinationEndPoint()). However, I notice that just the payload is getting sent without the Ethernet header. What am I missing here? Secondly, is it necessary to use a separate destination endpoint, or can I use m_socketPtr->local_endpoint() in the call to async_send_to (of course setting the destination MAC address in the constructor itself prior to calling bind)?


Solution

  • Attaching strace to the process provided a hint:

    strace  -e trace=network -p `pidof <process name>`
    

    I expected to find

    socket(PF_PACKET, SOCK_DGRAM, 1)
    

    But instead found

    socket(PF_PACKET, SOCK_RAW, 1)
    

    which led me to believe the 2nd parameter to the open call of socket class does not specify socket type but protocol family

    So I changed the protocol class from raw_protocol to datagram_protocol (and similar changes to endpoint classes) and it worked!

    class MyClass
    {
    public:
    MyClass(/*parameters*/);
    typedef boost::asio::generic::datagram_protocol datagram_protocol_t;
    typedef boost::asio::generic::basic_endpoint<datagram_protocol_t> datagram_endpoint_t;
    
    void SetDestinationEndPoint(int ifIndex, int protocol, const uint8_t *destinationMacAddress);
    
    private:
    boost::shared_ptr<datagram_protocol_t::socket> m_socketPtr;
    datagram_endpoint_t *m_pDestinationEndpoint;
    };