#include #include #include #include #if !defined(__OpenBSD__) #define __FAVOR_BSD #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) #include #endif #include #include #include #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 void cb_nodetraceroute(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 *filter) { 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 (!filter) filter = strndup(PCAP_FILTER, 256); if (pcap_compile(pcap, &bprog, filter, 0, 0) < 0) err(1, "capture: pcap_compile failed with filter %s : %s", 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); ggnet_set_traceroute(net, 1, ev_base, cb_nodetraceroute); } _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); } } static void cb_nodetraceroute(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_TRACE; pkt.name_addr = n->addr.s_addr; // XXX IN PROGRESS //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_debug("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_debug("llc packet !"); else { switch (ether_type) { case ETHERTYPE_IP: gg_log_debug("loop family AF_LINK IP"); ip = (struct ip *)((u_char *)ether + sizeof(struct ether_header)); ip_handle(ip, pend, wirelen); break; default: gg_log_debug("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_debug("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_debug("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_debug("unknown family %x !", family); break; } } #endif /* __OpenBSD__ */