/*
 * dns_matching.c  Drop DNS packets requesting DNS name contained in hash map
 *    For Linux, uses BCC, eBPF. See .py file.
 *
 * Copyright (c) 2016 Rudi Floren.
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 11-May-2016  Rudi Floren Created this.
 */

#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/if_packet.h>
#include <uapi/linux/ip.h>
#include <uapi/linux/in.h>
#include <uapi/linux/udp.h>
#include <bcc/proto.h>

#define ETH_LEN 14

struct dns_hdr_t
{
    uint16_t id;
    uint16_t flags;
    uint16_t qdcount;
    uint16_t ancount;
    uint16_t nscount;
    uint16_t arcount;
} BPF_PACKET_HEADER;


struct dns_query_flags_t
{
  uint16_t qtype;
  uint16_t qclass;
} BPF_PACKET_HEADER;

struct dns_char_t
{
    char c;
} BPF_PACKET_HEADER;

struct Key {
  unsigned char p[32];
};

struct Leaf {
  // Not really needed in this example
  unsigned char p[4];
};

BPF_TABLE("hash", struct Key, struct Leaf, cache, 128);

int dns_matching(struct __sk_buff *skb)
{
  u8 *cursor = 0;
  struct Key key = {};
  // Check of ethernet/IP frame.
  struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
  if(ethernet->type == ETH_P_IP) {

    // Check for UDP.
    struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));
    u16 hlen_bytes = ip->hlen << 2;
    if(ip->nextp == IPPROTO_UDP) {

      // Check for Port 53, DNS packet.
      struct udp_t *udp = cursor_advance(cursor, sizeof(*udp));
      if(udp->dport == 53){

        // Our Cursor + the length of our udp packet - size of the udp header
        // - the two 16bit values for QTYPE and QCLASS.
        u8 *sentinel = cursor + udp->length - sizeof(*udp) - 4;

        struct dns_hdr_t *dns_hdr = cursor_advance(cursor, sizeof(*dns_hdr));

        // Do nothing if packet is not a request.
        if((dns_hdr->flags >>15) != 0) {
          // Exit if this packet is not a request.
          return -1;
        }

        u16 i = 0;
        struct dns_char_t *c;
        // This unroll worked not in latest BCC version.
        for(u8 j = 0; i<255;i++){
          if (cursor == sentinel) goto end; c = cursor_advance(cursor, 1); key.p[i++] = c->c;
        }
        end:
        {}

        struct Leaf * lookup_leaf = cache.lookup(&key);

        // If DNS name is contained in our map, drop packet.
        if(lookup_leaf) {
          return 0;
        }
      }
    }
  }

  return -1;
}
