aboutsummaryrefslogblamecommitdiffstats
path: root/gg_sniff/pcap.c
blob: 72b9133458408e4e503ace35903f07c7b603fc3e (plain) (tree)























                             
                

                 


                      




                        
                        
                                                                     


                     
                                     



                                          


                                 

                 












                                 
 
                                                                  
                          
                                                                  
                                                  
                                                                               
                                             
                                            
                                                
                        
                                           
                                                

                      
                                          
                                                
      

                                                     
                                                                     



                                      
                        
                               



                                    



                          
 
     
                                                                      
                                               
 

                                
               
 



                                                            

















                                                              
                                                                     
                     


                                  




                                                                          
 


                                                

                     
 
           

 
      

                           

                        








                                                                    
                 
                                                                    
                                         

                        































































                                                               
                               

                                                                
 


                         
 



                        
      






























                                                           



                                                         
 
             
                                      
 

                                                           
 




                                                                          
             

                                           




































                                                                                                                 

 
             















                                                                   







                                                                  

                                                      
             

                                                           













                                                                   
                              
































































































                                                                
 

                                                                  





                                                                  
                                       













                                                                            
                                       



                                                                

 
             










                                   
             


                                                                           
                     







                                         







                                                                          



     
                     

                         








                                                    

 
             

                                                                          


                          
 
                                                
 

                                                                 
 


                                             
 
                              

 










                                                                                          
             

                                                                        




                          
 
                                       
 

                                                                 
 

                             



                                                                 





                                                                

          
                                        










                               
             

                                                                         





























                                                                 
 
                        
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/time.h>

#if !defined(__OpenBSD__)
#define __FAVOR_BSD
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>

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

#include <pcap.h>
#if defined(__linux__)
#include <pcap/sll.h>
#endif
#include <event.h>

#include <libglouglou.h>
#include <libggnet.h>

#define PCAP_SNAPLEN 100
#define PCAP_FILTER "not port 4430 and not port 4431 and not port 53"
#define PCAP_COUNT 20
#define PCAP_TO 300

#define NODE_MAX_WITHOUT_TIMEOUT 1000
#define NODE_TIMEOUT 60 // XXX conf ?
#define CONN_TIMEOUT 30 // XXX conf ?
#define CONN_TIMEOUT_UDP 30 // XXX conf ?
#define CONN_TIMEOUT_ICMP 30 // XXX conf ?
#define CONNTIMER 5 // XXX conf ?

struct phandler {
  pcap_handler f;
  int type;
};

struct _cap_t {
  pcap_t           *pcap;
  pcap_handler      handler;
  struct event     *ev;
  struct event     *conntimer_ev;
  struct timeval    conntimer_tv;
  struct gg_client *ggcli;
  struct ggnet     *net;
  int               pinvalid;
  int               ptruncated;
};

static pcap_t      *my_pcap_open_live(const char *, int, int, int,
    char *, u_int, u_int);
static void         ip_handle(struct ip *, const u_char *, u_int);
static void         conn_del(struct ggnet_conn *);
static void         ether_handle(struct ether_header *, const u_char *, u_int);
static pcap_handler	lookup_phandler(int);
static void         phandler_ether(u_char *,
    const struct pcap_pkthdr *, const u_char *);
#if defined(__OpenBSD__)
static void         phandler_loop(u_char *,
    const struct pcap_pkthdr *, const u_char *);
#endif
#if defined(__linux__)
static void         phandler_sll(u_char *,
    const struct pcap_pkthdr *, const u_char *);
#endif
static void         cb_pcap(int, short, void *);
static void         cb_conntimer(int, short, void *);
static void         cb_nodename(struct ggnet *, struct ggnet_node *);

static struct phandler phandlers[] = {
  { phandler_ether, DLT_EN10MB },
  { phandler_ether, DLT_IEEE802 },
#if defined(__OpenBSD__)
  { phandler_loop,  DLT_LOOP },
#endif
#if defined(__linux__)
  { phandler_sll,   DLT_LINUX_SLL },
#endif
  { NULL,           0 },
};

static struct _cap_t _cap;

  int
ggsniff_pcap_init(struct event_base *ev_base, struct gg_client *ggcli,
    struct ggnet *net, char *iface, int active)
{
  char errbuf[PCAP_ERRBUF_SIZE];
  struct bpf_program bprog;
  pcap_t *pcap;

#if defined(__OpenBSD__)
  if (!iface)
    err(1, "On OpenBSD you cannot listen on ANY interface");
#endif
  pcap = my_pcap_open_live(iface, PCAP_SNAPLEN, 1, PCAP_TO,
      errbuf, -1, 0);
  if (pcap == NULL)
    err(1, "capture: pcap_open_live failed on interface %s\n"
        "with snaplen %d : %s",
        iface, PCAP_SNAPLEN, errbuf);
  if (pcap_compile(pcap, &bprog, PCAP_FILTER, 0, 0) < 0)
    err(1, "capture: pcap_compile failed with filter %s : %s",
        PCAP_FILTER, pcap_geterr(pcap));
  if (pcap_setfilter(pcap, &bprog) < 0)
    err(1, "capture: pcap_setfilter failed : %s",
        pcap_geterr(pcap));

  _cap.pcap = pcap;
  _cap.handler = lookup_phandler(pcap_datalink(pcap));
  //_cap.tv.tv_sec = 0;
  //_cap.tv.tv_usec = PCAP_TO;
  fd_nonblock(pcap_fileno(pcap));
  _cap.ev = event_new(ev_base, pcap_fileno(pcap), EV_READ|EV_PERSIST,
      cb_pcap, NULL);
  //event_add(_cap.ev, &_cap->tv);
  event_add(_cap.ev, NULL);

  _cap.conntimer_tv.tv_sec = CONNTIMER;
  _cap.conntimer_tv.tv_usec = 0;
  _cap.conntimer_ev = evtimer_new(ev_base, cb_conntimer, NULL);
  if (evtimer_add(_cap.conntimer_ev, &_cap.conntimer_tv) == -1)
    gg_log_fatal("user: event_add conntimer failed: %s", strerror(errno));

  if (active)
    ggnet_set_dns(net, 1, ev_base, cb_nodename);

  _cap.ggcli = ggcli;
  _cap.net = net;

  return 1;
}

  void
ggsniff_pcap_shutdown(void)
{
  event_del(_cap.ev);
  pcap_close(_cap.pcap);
}

/*
 * reimplement pcap_open_live with more restrictions on the bpf fd :
 * - open device read only
 * - lock the fd
 * based on OpenBSD tcpdump, privsep_pcap.c v1.16
 */

  static pcap_t *
my_pcap_open_live(const char *dev, int slen, int promisc, int to_ms,
    char *ebuf, u_int dlt, u_int dirfilt)
{
#if defined(__OpenBSD__)
  struct bpf_version bv;
  u_int v;
  pcap_t *p;
  char		bpf[sizeof "/dev/bpf0000000000"];
  int		fd, n = 0;
  struct ifreq	ifr;

  p = xmalloc(sizeof(*p));
  bzero(p, sizeof(*p));

  /* priv part */

  do {
    snprintf(bpf, sizeof(bpf), "/dev/bpf%d", n++);
    fd = open(bpf, O_RDONLY);
  } while (fd < 0 && errno == EBUSY);
  if (fd < 0)
    return NULL;

  v = 32768;	/* XXX this should be a user-accessible hook */
  ioctl(fd, BIOCSBLEN, &v);

  strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
  if (ioctl(fd, BIOCSETIF, &ifr) < 0)
    return NULL;

  if (dlt != (u_int) -1 && ioctl(fd, BIOCSDLT, &dlt))
    return NULL;

  if (promisc)
    /* this is allowed to fail */
    ioctl(fd, BIOCPROMISC, NULL);
  if (ioctl(fd, BIOCSDIRFILT, &dirfilt) < 0)
    return NULL;

  /* lock the descriptor */
  if (ioctl(fd, BIOCLOCK, NULL) < 0)
    return NULL;

  /* end of priv part */

  /* fd is locked, can only use 'safe' ioctls */
  if (ioctl(fd, BIOCVERSION, &bv) < 0) {
    snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s",
        pcap_strerror(errno));
    return NULL;
  }

  if (bv.bv_major != BPF_MAJOR_VERSION ||
      bv.bv_minor < BPF_MINOR_VERSION) {
    snprintf(ebuf, PCAP_ERRBUF_SIZE,
        "kernel bpf filter out of date");
    return NULL;
  }

  p->fd = fd;
  p->snapshot = slen;

  /* Get the data link layer type. */
  if (ioctl(fd, BIOCGDLT, &v) < 0) {
    snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGDLT: %s",
        pcap_strerror(errno));
    return NULL;
  }
#if _BSDI_VERSION - 0 >= 199510
  /* The SLIP and PPP link layer header changed in BSD/OS 2.1 */
  switch (v) {

    case DLT_SLIP:
      v = DLT_SLIP_BSDOS;
      break;

    case DLT_PPP:
      v = DLT_PPP_BSDOS;
      break;
  }
#endif
  p->linktype = v;

  /* XXX hack from tcpdump */
  if (p->linktype == DLT_PFLOG && p->snapshot < 160)
    p->snapshot = 160;

  /* set timeout */
  if (to_ms != 0) {
    struct timeval to;
    to.tv_sec = to_ms / 1000;
    to.tv_usec = (to_ms * 1000) % 1000000;
    if (ioctl(p->fd, BIOCSRTIMEOUT, &to) < 0) {
      snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSRTIMEOUT: %s",
          pcap_strerror(errno));
      return NULL;
    }
  }

  if (ioctl(fd, BIOCGBLEN, &v) < 0) {
    snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s",
        pcap_strerror(errno));
    return NULL;
  }
  p->bufsize = v;
  p->buffer = (u_char *)malloc(p->bufsize);
  if (p->buffer == NULL) {
    snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
        pcap_strerror(errno));
    return NULL;
  }
  return p;
#else /* defined(__OpenBSD__) */
  return pcap_open_live(dev, slen, promisc, to_ms, ebuf);
#endif
}

  static void
cb_pcap(int fd, short why, void *data)
{
  gg_log_tmp("cb_pcap");
  pcap_dispatch(_cap.pcap, PCAP_COUNT, _cap.handler, NULL);

  /* reschedule */
  //if (event_add(&_cap->ev, &_cap->tv) == -1)
  //	gg_log_fatal("user: event_add pcap failed : %s", strerror(errno));
}

  static void
cb_conntimer(int fd, short why, void *data)
{
  struct ggnet_conn *c, *ctmp;
  struct ggnet_node *n, *ntmp;
  int i, to;

  gg_log_debug("ev_timer");
  ggnet_time_update(_cap.net, time(NULL));

  i = 0;
  LIST_FOREACH_SAFE(c, &_cap.net->conn_list, entry, ctmp) {
    switch (c->proto) {
      case IPPROTO_UDP:
        to = CONN_TIMEOUT_UDP;
        break;
      case IPPROTO_ICMP:
        to = CONN_TIMEOUT_ICMP;
        break;
      default:
        to = CONN_TIMEOUT;
        break;
    }
    if (_cap.net->time > c->lastseen + to)
      conn_del(c);
    else
      i++;
  }

  if (_cap.net->node_count > NODE_MAX_WITHOUT_TIMEOUT) {
    LIST_FOREACH_SAFE(n, &_cap.net->node_list, entry, ntmp) {
      if (n->used == 0 &&
          _cap.net->time > n->lastseen + NODE_TIMEOUT)
        ggnet_node_del(_cap.net, n);
    }
  }

  gg_log_debug("user: ev_timer leaving with %d active connections and %d active nodes", i, _cap.net->node_count);
  if (evtimer_add(_cap.conntimer_ev, &_cap.conntimer_tv) == -1)
    gg_log_fatal("user: event_add conntimer failed : %s", strerror(errno));
}

  static void
cb_nodename(struct ggnet *net, struct ggnet_node *n)
{
  struct gg_packet pkt;
  int len;

  len = strnlen(n->fqdn, GGNET_DNSNAME_MAX);
  if (len > 0) {
    pkt.ver = PACKET_VERSION;
    pkt.type = PACKET_NAME;
    pkt.name_addr = n->addr.s_addr;
    pkt.name_len = len;
    strncpy((char *)pkt.name_fqdn, n->fqdn, sizeof(pkt.name_fqdn));
    gg_client_send(_cap.ggcli, &pkt);
  }
}

/*
 * Parse an IP packet and descide what to do with it.
 * 'ip' is a pointer the the captured IP packet
 * 'pend' is a pointer to the end of the captured IP packet
 * 'wirelen' is the size of the IP packet off the wire
 */
#define NOTCAPTURED(v) ((u_char *)v > (u_char *)pend - sizeof(*v))
#define NOTRECEIVED(v) (wirelen < sizeof(v))
#define log_pinvalid(fmt, ...) \
  gg_log_info("ggsniff pinvalid: " fmt, ##__VA_ARGS__)
  static void
ip_handle(struct ip *ip, const u_char *pend, u_int wirelen)
{
  u_int		len, ip_hlen, off;
  const u_char	*cp;
  struct tcphdr	*tcph;
  struct udphdr	*udph;
  struct icmp	*icmp;
  struct in_addr	src, dst;
  u_int		src_port, dst_port;
  u_int		proto, close;
  int response;
  struct ggnet_conn	*conn;
  struct gg_packet pkt;

  if (NOTCAPTURED(ip)) {
    log_pinvalid("user: ip truncated (ip %x pend %x sizeof(ip) %d",
        ip, pend, sizeof(ip));
    _cap.ptruncated++;
    return;
  }

  if (ip->ip_v != IPVERSION) {
    log_pinvalid("user: invalid ip version");
    _cap.pinvalid++;
    return;
  }

  len = ntohs(ip->ip_len);
  if (wirelen < len) {
    log_pinvalid("user: ip too small");
    _cap.pinvalid++;
    len = wirelen;
  }

  ip_hlen = ip->ip_hl * 4;
  if (ip_hlen < sizeof(struct ip) || ip_hlen > len) {
    log_pinvalid("user: ip_hlen invalid, %d", ip_hlen);
    _cap.pinvalid++;
    return;
  }
  len -= ip_hlen;

  src.s_addr = ntohl(ip->ip_src.s_addr);
  dst.s_addr = ntohl(ip->ip_dst.s_addr);
  src_port = 0;
  dst_port = 0;
  proto = IPPROTO_IP;
  close = 0;

  off = ntohs(ip->ip_off);
  if ((off & IP_OFFMASK) == 0) {
    cp = (const u_char *)ip + ip_hlen;
    switch (ip->ip_p) {

      case IPPROTO_TCP:
        tcph = (struct tcphdr *)cp;
        if (NOTCAPTURED(&tcph->th_flags)) {
          log_pinvalid("user: tcp truncated");
          _cap.ptruncated++;
          return;
        }
        if (NOTRECEIVED(*tcph)) {
          log_pinvalid("user: tcp too small");
          _cap.pinvalid++;
          return;
        }
        src_port = ntohs(tcph->th_sport);
        dst_port = ntohs(tcph->th_dport);
        proto = IPPROTO_TCP;
        if ((tcph->th_flags & TH_FIN) &&
            (tcph->th_flags & TH_ACK))
          close = 1;
        break;

      case IPPROTO_UDP:
        udph = (struct udphdr *)cp;
        if (NOTCAPTURED(&udph->uh_dport)) {
          log_pinvalid("user: udp truncated, "
              "ip %x, udph %x, uh_port %x, pend %x, ip_hlen %d",
              ip, udph, &udph->uh_dport, pend, ip_hlen);
          _cap.ptruncated++;
          return;
        }
        if (NOTRECEIVED(*udph)) {
          log_pinvalid("user: udp too small");
          _cap.pinvalid++;
          return;
        }
        src_port = ntohs(udph->uh_sport);
        dst_port = ntohs(udph->uh_dport);
        proto = IPPROTO_UDP;
        break;

      case IPPROTO_ICMP:
        icmp = (struct icmp *)cp;
        if (NOTRECEIVED(*icmp)) {
          log_pinvalid("user: icmp too small");
          _cap.pinvalid++;
          return;
        }
        proto = IPPROTO_ICMP;
        break;

      default:
        gg_log_warn("user: unknown ip protocol !");
        break;
    }
  } else {
    /*
     * if this isn't the first frag, we're missing the
     * next level protocol header.
     */
    gg_log_tmp("user: got a fragmented ip packet !");
  }

  pkt.ver = PACKET_VERSION;
  conn = ggnet_conn_find(_cap.net, &src, src_port, &dst, dst_port,
      proto, &response);
  if (conn) {
    if (!close) {
      pkt.type = PACKET_DATA;
      pkt.data_connid = conn->id;
      GG_PKTDATA_SIZE_ENCODE(pkt.data_size, ip->ip_len, response);
      gg_client_send(_cap.ggcli, &pkt);
      ggnet_conn_data(_cap.net, conn, ip->ip_len, response);
    } else {
      conn_del(conn);
    }
  } else {
    if (!close) {
      conn = ggnet_conn_add(_cap.net, &src, src_port, &dst, dst_port, proto,
          ip->ip_len, -1);
      pkt.type = PACKET_NEWCONN;
      pkt.newconn_id = conn->id;
      pkt.newconn_src = src.s_addr;
      pkt.newconn_dst = dst.s_addr;
      pkt.newconn_proto = proto;
      GG_PKTDATA_SIZE_ENCODE(pkt.newconn_size, ip->ip_len, 0);
      gg_client_send(_cap.ggcli, &pkt);
    } else {
      gg_log_warn("user: captured connection close w/o open !");
    }
  }
}

  static void
conn_del(struct ggnet_conn *c)
{
  struct gg_packet pkt;

  pkt.ver = PACKET_VERSION;
  pkt.type = PACKET_DELCONN;
  pkt.delconn_id = c->id;
  gg_client_send(_cap.ggcli, &pkt);
  ggnet_conn_del(_cap.net, c);
}

  static void
ether_handle(struct ether_header *ether, const u_char *pend, u_int wirelen)
{
  struct ip *ip;
  u_short ether_type;

  wirelen -= sizeof(struct ether_header);

  ether_type = ntohs(ether->ether_type);
  if (ether_type <= ETHERMTU)
    gg_log_tmp("llc packet !");
  else {
    switch (ether_type) {
      case ETHERTYPE_IP:
        gg_log_tmp("loop family AF_LINK IP");
        ip = (struct ip *)((u_char *)ether + sizeof(struct ether_header));
        ip_handle(ip, pend, wirelen);
        break;
      default:
        gg_log_tmp("loop non ip packet !");
        break;
    }
  }
}

  static pcap_handler
lookup_phandler(int type)
{
  struct phandler *p;

  for (p = phandlers; p->f; ++p) {
    if (type == p->type)
      return p->f;
  }
  err(1, "user: unknown data link type 0x%x", type);
  /* NOTREACHED */
  return NULL;
}

  static void
phandler_ether(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
  struct ether_header *ep;
  const u_char *pend;
  u_int	len;

  gg_log_debug("user: pcap handler ethernet !");

  /* XXX here i assume that packets are alligned, which might not
   * be the case when using dump files, says tcpdump sources */

  ep = (struct ether_header *)p;
  pend = p + h->caplen;
  len = h->len - sizeof(struct ether_header);

  ether_handle(ep, pend, len);
}

/*
 * Handler for Linux cooked, used when capturing on any interface
 */
#if defined(__linux__)
#define LINUX_SLL_P_802_3	0x0001	/* Novell 802.3 frames without 802.2 LLC header */
#define LINUX_SLL_P_ETHERNET	0x0003	/* Ethernet */
#define LINUX_SLL_P_802_2	0x0004	/* 802.2 frames (not D/I/X Ethernet) */
#define LINUX_SLL_P_PPPHDLC	0x0007	/* PPP HDLC frames */
#define LINUX_SLL_P_CAN		0x000C	/* Controller Area Network */
#define LINUX_SLL_P_IRDA_LAP	0x0017	/* IrDA Link Access Protocol */

  static void
phandler_sll(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
  struct ip *ip;
  struct ether_header *ep;
  u_int family;
  const u_char *pend;
  u_int	len;

  gg_log_debug("user: phandler_sll !");

  /* XXX here i assume that packets are alligned, which might not
   * be the case when using dump files, says tcpdump sources */

  pend = p + h->caplen;
  len = h->len - SLL_HDR_LEN;

  family = ntohs(p[14]);
  if (family < 1536) { /* linux and wireshark are good for you */
    switch (family) {
      case LINUX_SLL_P_ETHERNET:
        ep = (struct ether_header *)((u_char *)p + SLL_HDR_LEN);
        ether_handle(ep, pend, len);
      default:
        gg_log_tmp("unknown family %x !", family);
        break;
    }
  } else {
    ip = (struct ip *)(p + SLL_HDR_LEN);
    ip_handle(ip, pend, len);
  }
}
#endif /* __linux__ */

/*
 * Handler for OpenBSD Loopback
 */
#if defined(__OpenBSD__)
#define NULL_HDRLEN 4

  static void
phandler_loop(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
  struct ip *ip;
  struct ether_header *ep;
  u_int family;
  const u_char *pend;
  u_int	len;

  gg_log_debug("user: pcap handler loop !");

  /* XXX here i assume that packets are alligned, which might not
   * be the case when using dump files, says tcpdump sources */

  pend = p + h->caplen;
  len = h->len - NULL_HDRLEN;

  memcpy((char *)&family, (char *)p, sizeof(family));
  family = ntohl(family);
  switch (family) {
    case AF_INET:
      gg_log_tmp("loop family AF_INET");
      ip = (struct ip *)(p + NULL_HDRLEN);
      ip_handle(ip, pend, len);
      break;
    case AF_LINK:
      ep = (struct ether_header *)((u_char *)p + NULL_HDRLEN);
      ether_handle(ep, pend, len);
      break;
    default:
      gg_log_tmp("unknown family %x !", family);
      break;
  }
}
#endif /* __OpenBSD__ */