/*
 * pktstuff.c
 * 
 * Ceci est un petit pgm bidon pour vérifier si on est capable de
 * mettre des données bidons dans les paquets tcp dans des places
 * insolites, en jouant sur la longueur des champs.
 *
 * Le pgm est basé sur libdnet et fragroute.
 *
 * Le pgm sniffe sur le loopback adresse pour des paquets envoyés à la
 * destination.
 *
 * Chaque paquet est sniffé, modifié pour ajouter l'information
 * supplémentaire après le vrai champ TCP dans le paquet IP, et
 * ensuite envoyé vers la destination en utilisant raw socket.
 *
 * Puisque c'est un "proof of concept" seulement, je ne tient pas
 * compte du cas où les données originales remplissent la trame au
 * complet. Faudrait faire la fragmentation dans ce cas-là.
 *
 * Also the communication is 1-way only. Would have to modify packets
 * coming and going for a full-fledged tunnel.
 *
 * La réponse vient retourne à notre machine comme d'habitude.
 *
 * Emprunté de Dug Song libdnet test pgm
 *
 * $Id: pktstuff.c,v 1.4 2003/06/23 02:25:57 asaph Exp $ */

#include "config.h"
#include <sys/types.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>

#include <pcap.h>
#include <dnet.h>

#include "pktstuff.h"
#include "pkt.h"
#include "pcaputil.h"

static int event_gotsig = 0;

struct tun {
    intf_t *intf;
    pcap_t *pcap;
    route_t *route;

    struct route_entry rtent;
    struct intf_entry *ifent;
    u_char buf[1024];
};

typedef struct tun tun_t;

struct fr_ctx {
    struct addr src;
    struct addr dst;
    struct addr smac;
    struct addr dmac;

    int mtu;

    arp_t *arp;
    eth_t *eth;
    intf_t *intf;
    route_t *route;
    tun_t *tun;
};

static struct fr_ctx ctx;

/*****************/
/* Stream buffer */
/*****************/

#define OPT_DATA_LEN 21
static char buffer[1024];	/* Buffer holding data to be sent */
int counter;




tun_t *tunn_close(tun_t * tun)
{
    /* Stop sniffing. */
    if (tun->pcap != NULL)
	pcap_close(tun->pcap);

    /* Delete loopback route. */
    if (tun->route != NULL) {
	if (route_delete(tun->route, &tun->rtent) < 0)
	    warnx("couldn't delete loopback route");
	route_close(tun->route);
    }
    /* Restore interface address and MTU. */
    if (tun->intf != NULL) {
	if (tun->ifent != NULL) {
	    if (intf_set(tun->intf, tun->ifent) < 0)
		warnx("couldn't restore loopback settings");
	}
	intf_close(tun->intf);
    }
    free(tun);

    return (NULL);
}


tun_t *tunn_open(struct addr * src, struct addr * dst, int mtu)
{
    struct tun *tun;
    struct arp_entry arpent;
    struct intf_entry ifent;
    arp_t *arp;

    if ((tun = calloc(1, sizeof(*tun))) == NULL)
	return (NULL);

    tun->ifent = (struct intf_entry *) tun->buf;
    tun->ifent->intf_len = sizeof(tun->buf);
    strlcpy(tun->ifent->intf_name, LOOPBACK_DEV,
	    sizeof(tun->ifent->intf_name));

    /* Get interface information. */
    if ((tun->intf = intf_open()) == NULL)
	return (NULL);

    if (intf_get(tun->intf, tun->ifent) < 0)
	return (tunn_close(tun));

    memcpy(&tun->rtent.route_dst, dst, sizeof(*dst));

    /* XXX - Linux sets the routed src IP regardless of assigned addr */
    addr_aton(LOOPBACK_ADDR, &tun->rtent.route_gw);

/*  code pour du non-Linux
 *	memcpy(&tun->rtent.route_gw, src, sizeof(*src));
 */
    /* Set interface address and MTU. */
    memset(&ifent, 0, sizeof(ifent));
    strcpy(ifent.intf_name, tun->ifent->intf_name);
    ifent.intf_flags = tun->ifent->intf_flags | INTF_FLAG_UP;
    ifent.intf_mtu = mtu;
    memcpy(&ifent.intf_addr, &tun->rtent.route_gw,
	   sizeof(ifent.intf_addr));

    if (intf_set(tun->intf, &ifent) < 0)
	return (tunn_close(tun));

    /* Delete any existing route for destination. */
    if ((tun->route = route_open()) == NULL)
	return (tunn_close(tun));
    route_delete(tun->route, &tun->rtent);

    /* Delete any existing ARP entry for destination. */
    if ((arp = arp_open()) != NULL) {
	memcpy(&arpent.arp_pa, dst, sizeof(*dst));
	arp_delete(arp, &arpent);
	arp_close(arp);
    }
    /* Add route for destination via loopback. */
    if (route_add(tun->route, &tun->rtent) < 0)
	return (tunn_close(tun));

    /* Set up to sniff on loopback. */
    if ((tun->pcap = pcap_open(tun->ifent->intf_name)) == NULL)
	return (tunn_close(tun));

    if (pcap_filter(tun->pcap, "ip dst %s", addr_ntoa(dst)) < 0)
	return (tunn_close(tun));
    return (tun);
}

/*
 * tcp_handler: stick some unauthorized data as filler in the tcp
 * options field
 *
 * pkt 	Ptr to input packet
 */
static void tcp_handler(struct pkt *pkt)
{
    /* static char tcp_data_add[] = "\x01\x00mon scrap a moi ya"; */
    char tcp_data_add[OPT_DATA_LEN] = "\x01\x00";
    /* Data added in options field - starts with */
    /* noop then end of options list */
    uint16_t len_don_tcp = sizeof(tcp_data_add) - 1;

    int len_tcph = 0;		/* Length tcp hdr field */
    int len_tcpd = 0;		/* Length of tcp data */
    int len_frame = 0;		/* Length of ether data */
    uint16_t ip_len_tmp = 0;	/* Temp - length pkt ip */
    struct pkt *new;

    /* Construire le buffer */
    memcpy(tcp_data_add + 2, buffer + counter, OPT_DATA_LEN - 3);
    memcpy(tcp_data_add + OPT_DATA_LEN - 1, "\x00", 1);
    counter += OPT_DATA_LEN - 3;
    if (counter > strlen(buffer))
	counter = 0;

    /* ajouter les nouvelles données à la fin du champs TCP option */

    len_tcph = pkt->pkt_tcp->th_off << 2;	/* size of Tcp hdr */
    len_frame = pkt->pkt_end - pkt->pkt_eth_data;
    /* Size of ethernet frame */

    /* If tcp hdr has space left, and ethernet frame also has room to grow */
    if (((len_tcph + len_don_tcp) <= TCP_HDR_LEN_MAX) &&
	(len_frame + len_don_tcp) <= ETH_MTU) {
	if ((new = pkt_dup(pkt)) == NULL) {
	    warnx("Error allocating new dup packet");
	    return;
	}

	/* If no tcp payload, then stick data on the end */
	if (new->pkt_tcp_data == NULL) {
	    memcpy(new->pkt_end, tcp_data_add, len_don_tcp);

	    /*
	     * otherwise copy new string into option field at end of tcp hdr
	     * and then copy original payload after inserted string
	     */
	} else {
	    memcpy(new->pkt_tcp_data, tcp_data_add, len_don_tcp);
	    len_tcpd = pkt->pkt_end - (u_char *) pkt->pkt_tcp_data;
	    /* Length of old tcp data payload */
	    memcpy(new->pkt_tcp_data + len_don_tcp, pkt->pkt_tcp_data,
		   len_tcpd);
	}

	/* Adjust the length fields */
	new->pkt_end += len_don_tcp;
	new->pkt_tcp->th_off += (len_don_tcp >> 2);
	ip_len_tmp = ntohs(new->pkt_ip->ip_len) + len_don_tcp;
	new->pkt_ip->ip_len = htons(ip_len_tmp);

	/* write out the modified packet */
	len_frame = new->pkt_end - (u_char *) new->pkt_eth_data;
	ip_checksum(new->pkt_ip, len_frame);

	warnx("Pkt tcp modifie ecrit ip-id %x", ntohs(new->pkt_ip->ip_id));
	eth_send(ctx.eth, new->pkt_eth, len_frame + ETH_HDR_LEN);
	pkt_free(new);

    } else {			/* Pas de place pour nos données dans le hdr */
	warnx("Pas de place dans le hdr TCP pour l'ajout, ip_id %x, "
	      "tcp hdr leng=%i", ntohs(pkt->pkt_ip->ip_id), len_tcph);

	/* On ecrit le pkt original tel quel */
	ip_checksum(pkt->pkt_ip, len_frame);
	eth_send(ctx.eth, pkt->pkt_eth, len_frame + ETH_HDR_LEN);
    }

    return;
}


/*
 * udp_handler: put data in UDP source port, consequently, we only
 *     have a one way tunnel
 *
 * pkt Ptr to input packet
 */

static void udp_handler(struct pkt *pkt)
{
    static char udp_data_add[] = "AB";
    uint16_t len_don_udp = sizeof(udp_data_add) - 1;

    int len_frame = 0;		/* Length of frame */
    uint16_t ip_len_tmp = 0;	/* Temp - length pkt ip */
    struct pkt *new;

    len_frame = pkt->pkt_end - pkt->pkt_eth_data;
    /* Size of ethernet frame */

    /* If have room in the frame */
    if ((len_frame + len_don_udp) <= ETH_MTU) {
	if ((new = pkt_dup(pkt)) == NULL) {
	    warnx("Error allocating new dup packet");
	    return;
	}

	memcpy(new->pkt_t_hdr_u.udp, udp_data_add, len_don_udp);
	/* Copy the new string into udp source port */

	/* write out the modified packet */
	len_frame = new->pkt_end - (u_char *) new->pkt_eth_data;
	ip_checksum(new->pkt_ip, len_frame);
	warnx("Pkt udp modifie ecrit, ip_id %x",
	      ntohs(new->pkt_ip->ip_id));
	eth_send(ctx.eth, new->pkt_eth, len_frame + ETH_HDR_LEN);
	pkt_free(new);

    } else {			/* Pas de place pour nos données dans la trame */
	warnx("Pas de place dans la trame UDP pour l''ajout, ip_id %x,"
	      " frame leng=%i", ntohs(pkt->pkt_ip->ip_id), len_frame);

	/* on ecrit le pkt original tel quel */
	ip_checksum(pkt->pkt_ip, len_frame);
	eth_send(ctx.eth, pkt->pkt_eth, len_frame + ETH_HDR_LEN);
    }

    return;
}


static int main_close(void)
{
    if (ctx.tun != NULL)
	tunn_close(ctx.tun);
    if (ctx.route != NULL)
	route_close(ctx.route);
    if (ctx.intf != NULL)
	intf_close(ctx.intf);
    if (ctx.eth != NULL)
	eth_close(ctx.eth);
    if (ctx.arp != NULL)
	arp_close(ctx.arp);
    return (-1);
}

/*
 * Copy sniffed data into a new packet. Verify that the packet is
 * well-formed. If the packet is TCP, or UDP, then stuff some
 * unauthorized data as filler into the packet and send it on its way.
 */

static void
main_process(u_char * client_data, const struct pcap_pkthdr *pcpkt,
	     const u_char * pktdata)
{

    /* locals */
    struct pkt *pkt;
    int len_cap = pcpkt->caplen;	/* Length of captured data */

    if (pktdata == NULL)
	return;

    if (len_cap > PKT_BUF_LEN) {
	warnx("Dropping oversize packet");
	return;
    }

    if ((pkt = pkt_new()) == NULL) {
	warnx("Error allocating new packet");
	return;
    }

    /* Copy received data into a new pkt element */
    memcpy(pkt->pkt_data, pktdata, len_cap);
    pkt->pkt_end = pkt->pkt_data + len_cap;

    pkt_decorate(pkt);		/* Verify that pkt is well-constructed */


    /* s'assurer que la réponse revient sur l'adresse normale de notre PC */

    eth_pack_hdr(pkt->pkt_eth, ctx.dmac.addr_eth,
		 ctx.smac.addr_eth, ETH_TYPE_IP);

    pkt->pkt_ip->ip_src = ctx.src.addr_ip;

    /* Switch on type of packet */
    switch (ntohs(pkt->pkt_eth->eth_type)) {

	/* IP packet */
    case ETH_TYPE_IP:

	if (pkt->pkt_ip == NULL)	/* Ignore malformed pkt */
	    break;

	/* switch on the IP protocol */
	switch (pkt->pkt_ip->ip_p) {

	    /* Tcp packet */
	case IPPROTO_TCP:

	    if (pkt->pkt_tcp == NULL)	/* Ignore malformed pkts */
		break;

	    tcp_handler(pkt);
	    break;

	    /* Udp packet */
	case IP_PROTO_UDP:
	    if (pkt->pkt_udp == NULL)	/* Ignore malformed pkts */
		break;

	    udp_handler(pkt);
	    break;

	    /* Icmp packet: just write it out */
	default:
	    warnx("Pkt icmp ecrit sans modifications, ip_id %x",
		  ntohs(pkt->pkt_ip->ip_id));
	    ip_checksum(pkt->pkt_ip, len_cap);
	    eth_send(ctx.eth, pkt->pkt_eth, len_cap);
	    break;

	}			/* end switch on IP Protocol */
	break;


	/* Some other type of packet - we just write it out as is */
    default:
	warnx("Pkt non-ip ecrit sans modifications");
	eth_send(ctx.eth, pkt->pkt_eth, len_cap);
	break;
    }

    pkt_free(pkt);
    return;
}



static void main_signal(int sig)
{
    warnx("exiting on signal %d", sig);
    event_gotsig++;
}

static void main_init(const char *dst)
{
    struct arp_entry arpent;
    struct intf_entry ifent;
    struct route_entry rtent;

    signal(SIGINT, main_signal);
    signal(SIGTERM, main_signal);

    srand(time(NULL));

    if (addr_aton(dst, &ctx.dst) < 0)
	err(1, "destination address invalid");

    if (ctx.dst.addr_bits != IP_ADDR_BITS)
	errx(1, "only /32 destinations supported at this time");

    pkt_init(128);

    if ((ctx.arp = arp_open()) == NULL ||
	(ctx.intf = intf_open()) == NULL ||
	(ctx.route = route_open()) == NULL)
	err(1, "couldn't open kernel networking interfaces");

    /* Find outgoing interface, addresses, and MTU. */
    ifent.intf_len = sizeof(ifent);
    if (intf_get_dst(ctx.intf, &ifent, &ctx.dst) < 0)
	err(1, "couldn't determine outgoing interface");

    memcpy(&ctx.src, &ifent.intf_addr, sizeof(ctx.src));
    ctx.src.addr_bits = IP_ADDR_BITS;
    memcpy(&ctx.smac, &ifent.intf_link_addr, sizeof(ctx.smac));
    ctx.mtu = ifent.intf_mtu;

    /* Open outgoing interface for sending. */
    if ((ctx.eth = eth_open(ifent.intf_name)) == NULL)
	err(1, "couldn't open %s for sending", ifent.intf_name);

    /* Find destination MAC address. */
    memcpy(&arpent.arp_pa, &ctx.dst, sizeof(arpent.arp_pa));

    if (arp_get(ctx.arp, &arpent) < 0) {
	memcpy(&rtent.route_dst, &ctx.dst, sizeof(rtent.route_dst));

	if (route_get(ctx.route, &rtent) < 0)
	    err(1, "no route to %s", addr_ntoa(&rtent.route_dst));

	memcpy(&arpent.arp_pa, &rtent.route_gw, sizeof(arpent.arp_pa));

	if (arp_get(ctx.arp, &arpent) < 0)
	    err(1, "no ARP entry for %s", addr_ntoa(&arpent.arp_pa));
    }
    memcpy(&ctx.dmac, &arpent.arp_ha, sizeof(ctx.dmac));

    /* Setup our tunnel. */
    if ((ctx.tun = tunn_open(&ctx.src, &ctx.dst, ctx.mtu)) == NULL)
	err(1, "couldn't initialize tunnel interface");
}


// Data sent on lo will act as upstream provider,
// puts a lot of responsibility on endpoint decoder
// Local socket will provide data to insert into
// upstream

int packet_stuff(char *dst_ip)
{
    int i = 0;
    char temp[] =
	"123456789012345678901234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ09876543210987654321";

    strcpy(buffer, temp);
    counter = 0;

    main_init(dst_ip);

    while (!event_gotsig) {
	if (pcap_dispatch
	    (ctx.tun->pcap, -1, &main_process, (u_char *) & ctx) < 0) {
	    err(1, "Error in pcap loop - EXITING: %s",
		pcap_geterr(ctx.tun->pcap));
	}
    }

    main_close();
    exit(0);
}
