Question

I recently discover the raw sockets and I currently trying to capture a DNS packet (with the libcap library) sent with the host command and to reply to it before the DNS server with a wrong address. Unfortunately, it doesn't seem to work... I can see my packet with tcpdump but it is not pass to the host command. Here is the code I use for my test :

#include <arpa/inet.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>

typedef unsigned int u_int;
typedef unsigned short u_short;
typedef unsigned char u_char;

#include <pcap/pcap.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct dnshdr {
    uint16_t id;
    uint16_t flags;
    uint16_t nquestion;
    uint16_t nanswer;
    uint16_t nauthority;
    uint16_t nadditional;
};

struct msg {
    struct ethhdr eth;
    struct iphdr ip;
    struct udphdr udp;
    struct dnshdr dns;
    unsigned char * data;
    size_t dlen;
};

static void msg_init(uint8_t *, struct pcap_pkthdr const *, uint8_t const *);
static void msg_to_pkt(struct msg *, unsigned char *);
static uint16_t msg_len(struct msg *);
static struct msg * msg_cap(void);


static void
msg_init(uint8_t * msg, struct pcap_pkthdr const * h, uint8_t const * bytes)
{
    struct msg * self;
    size_t offset;

    self = (struct msg *)msg;
    offset = 0;
    memcpy(&self->eth, bytes + offset, sizeof self->eth);
    offset += sizeof self->eth;
    memcpy(&self->ip, bytes + offset, sizeof self->ip);
    offset += self->ip.ihl * 4;
    memcpy(&self->udp, bytes + offset, sizeof self->udp);
    offset += sizeof self->udp;
    memcpy(&self->dns, bytes + offset, sizeof self->dns);
    offset += sizeof self->dns;
    self->dlen = h->len - offset;
    self->data = malloc(self->dlen);
    if (self->data == NULL) {
        perror("malloc");
        free(self);
        exit(EXIT_FAILURE);
    }
    memcpy(self->data, bytes + offset, self->dlen); 
}


static void
msg_to_pkt(struct msg * self, unsigned char * pkt)
{
    memcpy(pkt, &self->eth, sizeof self->eth);
    pkt += sizeof self->eth;
    memcpy(pkt, &self->ip, sizeof self->ip);
    pkt += sizeof self->ip;
    memcpy(pkt, &self->udp, sizeof self->udp);
    pkt += sizeof self->udp;
    memcpy(pkt, &self->dns, sizeof self->dns);
    pkt += sizeof self->dns;
    memcpy(pkt, self->data, self->dlen);
}


static uint16_t
msg_len(struct msg * self)
{
    uint16_t len;

    len = 0;
    len += sizeof self->eth;
    len += sizeof self->ip;
    len += sizeof self->udp;
    len += sizeof self->dns;
    len += self->dlen;
    return len;
}


static struct msg *
msg_cap(void)
{
    struct bpf_program filter;
    pcap_t * p;
    void * msg;
    char errbuf[PCAP_ERRBUF_SIZE];

    p = pcap_create("eth0", errbuf);
    if (p == NULL) {
        pcap_perror(p, "pcap_create");
        return NULL;
    }
    if (pcap_activate(p) < 0) {
        pcap_perror(p, "pcap_activate");
        return NULL;
    }
    if (pcap_compile(p, &filter, "dst port 53 and udp", 1,
    PCAP_NETMASK_UNKNOWN) < 0) {
        pcap_perror(p, "pcap_compile");
        return NULL;
    }
    if (pcap_setfilter(p, &filter) < 0) {
        pcap_perror(p, "pcap_setfilter");
        return NULL;
    }
    msg = malloc(sizeof (struct msg));
    if (msg == NULL) {
        perror("malloc");
        return NULL;
    }
    if (pcap_loop(p, 1, &msg_init, msg) < 0) {
        pcap_perror(p, "pcap_loop");
        free(msg);
        return NULL;
    }
    return msg;
}


uint16_t
cksum(void const * buf, size_t nbytes)
{
    uint32_t cs = 0;
    uint16_t const * p;

    p = buf;
    while (nbytes > 1) {
        cs += *p++;
        nbytes -= 2;
    }
    if (nbytes == 1) {
        cs += *(uint8_t *)p;
    }
    while ((cs >> 16) != 0) {
        cs = (cs & 0xFFFF) + (cs >> 16);
    }
    return ~(uint16_t)cs;
}


int
main(void)
{
    struct msg * msg;
    struct sockaddr_ll sll;
    char resp[] = "\xc0\x0c\0\1\0\1\0\0\x02\x65\0\4\x7f\0\0\1";
    char ethaddr[ETH_ALEN];
    unsigned char * tmp;
    size_t const rlen = sizeof resp - 1;
    size_t mlen;
    uint32_t ipaddr;
    uint16_t port;
    int s;

    s = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) {
        perror("socket");
        return EXIT_FAILURE;
    }
    msg = msg_cap();
    if (msg == NULL) {
        return EXIT_FAILURE;
    }

    /* add DNS response */
    tmp = malloc(msg->dlen + rlen);
    if (tmp == NULL) {
        perror("malloc");
        return EXIT_FAILURE;
    }
    memcpy(tmp, msg->data, msg->dlen);
    memcpy(tmp + msg->dlen, resp, rlen);
    free(msg->data);
    msg->data = tmp;
    msg->dlen += rlen;
    mlen = msg_len(msg);

    /* reverse Ethernet address */
    memcpy(ethaddr, msg->eth.h_dest, ETH_ALEN);
    memcpy(msg->eth.h_dest, msg->eth.h_source, ETH_ALEN);
    memcpy(msg->eth.h_source, ethaddr, ETH_ALEN);

    /* reverse IP address and change some fields */
    ipaddr = msg->ip.daddr;
    msg->ip.daddr = msg->ip.saddr;
    msg->ip.saddr = ipaddr;
    msg->ip.tos = 0;
    msg->ip.ttl = 53;
    msg->ip.id = htons(9999);
    msg->ip.tot_len = htons(mlen - sizeof msg->eth);
    msg->ip.check = 0;
    msg->ip.check = cksum(&msg->ip, sizeof msg->ip);

    /* reverse UDP ports and change some fields */
    port = msg->udp.dest;
    msg->udp.dest = msg->udp.source;
    msg->udp.source = port;
    msg->udp.len = htons(mlen - sizeof msg->eth - sizeof msg->ip);
    msg->udp.check = 0;

    /* change DNS flags and nanswer fields */
    msg->dns.flags = htons(0x8180);
    msg->dns.nanswer = htons(1);

    /* alloc buffer and send the packet */
    tmp = malloc(mlen);
    if (tmp == NULL) {
        perror("malloc");
        return EXIT_FAILURE;
    }
    msg_to_pkt(msg, tmp);
    sll.sll_family = AF_PACKET;
    sll.sll_protocol = htons(ETH_P_IP);
    sll.sll_ifindex = if_nametoindex("eth0");
    sll.sll_hatype = 1;
    sll.sll_pkttype = PACKET_HOST;
    sll.sll_halen = ETH_ALEN;
    memcpy(&sll.sll_addr, msg->eth.h_dest, ETH_ALEN);
    if (sendto(s, tmp, mlen, 0, (struct sockaddr *)&sll, sizeof sll) < 0) {
        perror("sendto");
        return EXIT_FAILURE;
    }
    close(s);
    return 0;
}

Is there something wrong in it ?

Was it helpful?

Solution

Ok, after some further tests and headaches, I finally found the origin of my problem. In fact, there were two problems.

First of all, I was thinking that when the sll_pkttype field is set to PACKET_HOST it means that the packet is sent to localhost. But, after read back packet(7) it appears that this field is only used when receiving packets. In other words, my packet was sent to my router and was discarded because the MAC destination address was wrong.

Next, even if I was changing the MAC destination address, the packet was not sending back from my router to my computer. This was a NAPT problem. Because the packet I capture open an UDP session in the NAT table of my router, it is not possible for me to send a packet with the address of my computer as the destination IP address and the same destination port as the one kept in the NAT table. So, I changed the destination address to the external address of my router and it does the trick !

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top