简体   繁体   中英

AF_PACKET Socket Not Sending Empty UDP Packet With SOCK_RAW In C

I am trying to create a test program in C on Linux (Ubuntu 18.04) that sends an empty UDP packet via an AF_PACKET/PF_PACKET socket using SOCK_RAW . In this program, I know the source and destination MAC addresses and IPs. However, it is not working and I'm not sure what I'm doing wrong. I wasn't able to find much resources online regarding this issue sadly since most of the threads I've found are more so for receiving packets on AF_PACKET sockets. The program also states it sends the correct amount of bytes to the destination. Though, I don't see the packets when using tcpdump both on the source and destination VMs.

Here is the program's code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/if.h>
#include <linux/if_packet.h>
#include <linux/udp.h>
#include <net/ethernet.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <inttypes.h>

#define REDIRECT_HEADER

#include "csum.h"

#define MAX_PCKT_LENGTH 65535

int main()
{
    int sockfd;
    struct sockaddr_ll dst;
    char pckt[MAX_PCKT_LENGTH];

    sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);

    if (sockfd <= 0)
    {
        perror("socket");

        exit(1);
    }

    dst.sll_family = AF_PACKET;
    dst.sll_protocol = ETH_P_IP;

    if ((dst.sll_ifindex = if_nametoindex("ens18")) == 0)
    {
        fprintf(stdout, "Interface 'ens18' not found.\n");

        exit(1);
    }

    // Do destination ethernet MAC (ae:21:14:4b:3a:6d).
    dst.sll_addr[0] = 0xAE;
    dst.sll_addr[1] = 0x21;
    dst.sll_addr[2] = 0x14;
    dst.sll_addr[3] = 0x4B;
    dst.sll_addr[4] = 0x3A;
    dst.sll_addr[5] = 0x6D;
    dst.sll_halen = 6;

    // I tried doing this with and without bind. Still not working.
    if (bind(sockfd, (struct sockaddr *)&dst, sizeof(dst)) < 0)
    {
        perror("bind");

        exit(1);
    }

    struct ethhdr *ethhdr = (struct ethhdr *) (pckt);
    struct iphdr *iphdr = (struct iphdr *) (pckt + sizeof(struct ethhdr));
    struct udphdr *udphdr = (struct udphdr *) (pckt + sizeof(struct ethhdr) + sizeof(struct iphdr));
    unsigned char *data = (unsigned char *) (pckt + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr));

    // Do source ethernet MAC (1a:c4:df:70:d8:a6).
    ethhdr->h_source[0] = 0x1A;
    ethhdr->h_source[1] = 0xC4;
    ethhdr->h_source[2] = 0xDF;
    ethhdr->h_source[3] = 0x70;
    ethhdr->h_source[4] = 0xD8;
    ethhdr->h_source[5] = 0xA6;

    // Copy destination MAC to sockaddr_ll.
    memcpy(ethhdr->h_dest, dst.sll_addr, 6);

    // Protocol.
    ethhdr->h_proto = ETH_P_IP;

    // Fill out ip header.
    iphdr->ihl = 5;
    iphdr->version = 4;
    iphdr->frag_off = 0;
    iphdr->id = rand();
    iphdr->protocol = IPPROTO_UDP;
    iphdr->tos = 0x0;
    iphdr->ttl = 64;
    iphdr->saddr = inet_addr("10.50.0.3");
    iphdr->daddr = inet_addr("10.50.0.4");
    iphdr->tot_len = sizeof(struct iphdr) + sizeof(struct udphdr);
    iphdr->check = 0;
    iphdr->check = ip_fast_csum(iphdr, iphdr->ihl);

    // Fill out UDP header.
    udphdr->source = htons(27000);
    udphdr->dest = htons(27015);
    udphdr->len = htons(sizeof(struct udphdr));
    udphdr->check = 0;
    udphdr->check = csum_tcpudp_magic(iphdr->saddr, iphdr->daddr, sizeof(struct udphdr), IPPROTO_UDP, csum_partial(udphdr, sizeof(struct udphdr), 0));

    // Send packet
    uint16_t sent;

    if ((sent = sendto(sockfd, pckt, iphdr->tot_len + sizeof(struct ethhdr), 0, (struct sockaddr *)&dst, sizeof(dst))) < 0)
    {
        perror("sendto");
    }

    fprintf(stdout, "Sent %d of data.\n", sent);

    close(sockfd);

    exit(0);
}

Both the source and destination servers are VMs on my home server (both running Ubuntu 18.04).

Here is the main interface of my source VM:

ens18: flags=323<UP,BROADCAST,RUNNING,PROMISC>  mtu 1500
        inet 10.50.0.3  netmask 255.255.255.0  broadcast 10.50.0.255
        inet6 fe80::18c4:dfff:fe70:d8a6  prefixlen 64  scopeid 0x20<link>
        ether 1a:c4:df:70:d8:a6  txqueuelen 1000  (Ethernet)
        RX packets 1959766813  bytes 1896024793559 (1.8 TB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1936101432  bytes 1333123918522 (1.3 TB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Here is the main interface of my destination VM:

ens18: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.50.0.4  netmask 255.255.255.0  broadcast 10.50.0.255
        inet6 fe80::ac21:14ff:fe4b:3a6d  prefixlen 64  scopeid 0x20<link>
        ether ae:21:14:4b:3a:6d  txqueuelen 1000  (Ethernet)
        RX packets 1032069029  bytes 1251754298166 (1.2 TB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 74446483  bytes 9498785163 (9.4 GB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

I tried setting the source VM's 'ens18' interface to promiscuous mode, but that didn't make any difference. I don't think that would matter in this case, though. I also want to use SOCK_RAW in this case because I want to gain more experience and I don't want the kernel to do anything to the packets (I read that using AF_PACKET + SOCK_RAW will result in the kernel not messing with the packets).

With that said, I have an additional question. How would I go about getting the MAC address of an IP that isn't on the network/bound to an interface (eg a destination IP going to a server outside of my network)? I would assume I'd have to send an ARP request. If so, do I just send an ARP request to the destination server and get the MAC address before submitting each packet via AF_PACKET + SOCK_RAW ?

Any help is highly appreciated and thank you for your time!

I was able to figure out the issue. I wasn't converting the Ethernet protocol ( ETH_P_IP ) to network byte order via htons() since it's a big endian. With that said, I had to convert iphdr->total_len to network byte order as well. Otherwise, the IP header's total length would be incorrect according to tcpdump . I didn't do this in other programs I made and it worked okay. Therefore, I'd assume the kernel converted the IP header's total length to network byte order automatically.

Since I'm using an AF_PACKET socket to send, I have to do things the kernel would normally do.

Here's the program's final code for anyone wondering:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/if.h>
#include <linux/if_packet.h>
#include <linux/udp.h>
#include <net/ethernet.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <inttypes.h>

#define REDIRECT_HEADER

#include "csum.h"

#define MAX_PCKT_LENGTH 65535

int main()
{
    int sockfd;
    struct sockaddr_ll dst;
    char pckt[MAX_PCKT_LENGTH];

    sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);

    if (sockfd <= 0)
    {
        perror("socket");

        exit(1);
    }

    dst.sll_family = PF_PACKET;
    dst.sll_protocol = htons(ETH_P_IP);

    if ((dst.sll_ifindex = if_nametoindex("ens18")) == 0)
    {
        fprintf(stdout, "Interface 'ens18' not found.\n");

        exit(1);
    }

    // Do destination ethernet MAC (ae:21:14:4b:3a:6d).
    dst.sll_addr[0] = 0xAE;
    dst.sll_addr[1] = 0x21;
    dst.sll_addr[2] = 0x14;
    dst.sll_addr[3] = 0x4B;
    dst.sll_addr[4] = 0x3A;
    dst.sll_addr[5] = 0x6D;
    dst.sll_halen = ETH_ALEN;

    // I tried doing this with and without bind. Still not working.
    if (bind(sockfd, (struct sockaddr *)&dst, sizeof(dst)) < 0)
    {
        perror("bind");

        exit(1);
    }

    struct ethhdr *ethhdr = (struct ethhdr *) (pckt);
    struct iphdr *iphdr = (struct iphdr *) (pckt + sizeof(struct ethhdr));
    struct udphdr *udphdr = (struct udphdr *) (pckt + sizeof(struct ethhdr) + sizeof(struct iphdr));
    unsigned char *data = (unsigned char *) (pckt + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr));

    // Do source ethernet MAC (1a:c4:df:70:d8:a6).
    ethhdr->h_source[0] = 0x1A;
    ethhdr->h_source[1] = 0xC4;
    ethhdr->h_source[2] = 0xDF;
    ethhdr->h_source[3] = 0x70;
    ethhdr->h_source[4] = 0xD8;
    ethhdr->h_source[5] = 0xA6;

    for (int i = 0; i < 30; i++)
    {
        memcpy(data + i, "b", 1);
    }

    // Copy destination MAC to sockaddr_ll.
    memcpy(ethhdr->h_dest, dst.sll_addr, ETH_ALEN);

    // Protocol.
    ethhdr->h_proto = htons(ETH_P_IP);

    // Fill out ip header.
    iphdr->ihl = 5;
    iphdr->version = 4;
    iphdr->frag_off = 0;
    iphdr->id = htons(0);
    iphdr->protocol = IPPROTO_UDP;
    iphdr->tos = 0x0;
    iphdr->ttl = 64;
    iphdr->saddr = inet_addr("10.50.0.3");
    iphdr->daddr = inet_addr("10.50.0.4");
    iphdr->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + 30);
    iphdr->check = 0;
    iphdr->check = ip_fast_csum(iphdr, iphdr->ihl);

    // Fill out UDP header.
    udphdr->source = htons(27000);
    udphdr->dest = htons(27015);
    udphdr->len = htons(sizeof(struct udphdr) + 30);
    udphdr->check = 0;
    udphdr->check = csum_tcpudp_magic(iphdr->saddr, iphdr->daddr, sizeof(struct udphdr) + 30, IPPROTO_UDP, csum_partial(udphdr, sizeof(struct udphdr) + 30, 0));

    // Send packet
    uint16_t sent;
    int len = ntohs(iphdr->tot_len) + sizeof(struct ethhdr) + 30;

    if ((sent = sendto(sockfd, pckt, len, 0, (struct sockaddr *)&dst, sizeof(dst))) < 0)
    //if ((sent = write(sockfd, pckt, len)) < 0)
    {
        perror("sendto");
    }

    fprintf(stdout, "Sent %d of data. %d is IPHdr len. %d is len.\n", sent, iphdr->tot_len, len);

    close(sockfd);

    exit(0);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM