From 0ab94d648289babfbeade80b0ce2d566e2518ed8 Mon Sep 17 00:00:00 2001 From: laurent Date: Tue, 27 Mar 2012 01:21:50 +0200 Subject: initial import from my hg repository --- glougloud/user.c | 838 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 838 insertions(+) create mode 100644 glougloud/user.c (limited to 'glougloud/user.c') diff --git a/glougloud/user.c b/glougloud/user.c new file mode 100644 index 0000000..ac024c6 --- /dev/null +++ b/glougloud/user.c @@ -0,0 +1,838 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "glougloud.h" +#include "imsgev.h" + +/* XXX a user process should be able to have multiple clients, so that we + * can send the same data to multiple machines with almost overhead */ + +#define NULL_HDRLEN 4 // XXX portable ? +#define NODE_MAX_WITHOUT_TIMEOUT 1000 +#define NODE_TIMEOUT 300 // XXX conf ? +#define CONN_TIMEOUT 300 // XXX conf ? +#define CONN_TIMEOUT_UDP 20 // XXX conf ? +#define CONN_TIMEOUT_ICMP 10 // XXX conf ? +#define CONN_FREEIDS_COUNT 65536 /* 2^16 as long as freeids are u_int16_t */ +#define CONNTIMER 5 // XXX conf ? +#define PACKET_VERSION 1 + +struct node { + LIST_ENTRY(node) entry; + struct in_addr addr; + time_t lastseen; + int used; + short namelen; +#define NODENAME_WAITING -1 +#define NODENAME_FAILED -2 + char *name; +}; + +enum connstate { + CONNSTATE_ESTABLISHED, + CONNSTATE_TCPFIN, + CONNSTATE_TCPFIN2 +}; + +struct conn { + LIST_ENTRY(conn) entry; + u_int id; + enum connstate state; + struct node *src; + u_int src_port; + struct node *dst; + u_int dst_port; + u_int proto; + u_int size; + time_t lastseen; +}; + +struct user { + LIST_ENTRY(user) entry; + struct sockaddr_in addr; +}; + +struct capture { + int fd; + struct imsgev iev; + + pcap_t *pcap; + struct event pcap_ev; + struct timeval pcap_tv; + pcap_handler pcap_handler; + + LIST_HEAD(, conn) conn_list; + LIST_HEAD(, node) node_list; + int node_count; + u_int16_t conn_freeids[CONN_FREEIDS_COUNT]; + int conn_freeids_ptr; + struct event conntimer_ev; + struct timeval conntimer_tv; + time_t time; + + int pinvalid; + int ptruncated; +}; + +struct packet { + u_int8_t ver; + u_int8_t type; +/* XXX nicer way for _SIZE ... ? */ +#define PACKET_NEWCONN 0 +#define PACKET_NEWCONN_SIZE (2 + sizeof((struct packet *)0)->pdat.newconn) +#define PACKET_DELCONN 1 +#define PACKET_DELCONN_SIZE (2 + sizeof((struct packet *)0)->pdat.delconn) +#define PACKET_DATA 2 +#define PACKET_DATA_SIZE (2 + sizeof((struct packet *)0)->pdat.data) +#define PACKET_NAME 3 +#define PACKET_NAME_SIZE (2 + sizeof((struct packet *)0)->pdat.name) +#define PACKET_EXTRA_SIZEMAX 256 + + union { + struct newconn { + u_int16_t id; + u_int32_t src; + u_int32_t dst; + u_int8_t proto; + u_int8_t size; + } newconn; + struct delconn { + u_int16_t id; + } delconn; + struct data { + u_int16_t connid; + u_int8_t size; + } data; + struct name { + u_int32_t addr; + u_int8_t len; + } name; + } pdat; +#define newconn_id pdat.newconn.id +#define newconn_src pdat.newconn.src +#define newconn_dst pdat.newconn.dst +#define newconn_proto pdat.newconn.proto +#define newconn_size pdat.newconn.size +#define delconn_id pdat.delconn.id +#define data_connid pdat.data.connid +#define data_size pdat.data.size +#define name_addr pdat.name.addr +#define name_len pdat.name.len + + u_char extra[PACKET_EXTRA_SIZEMAX]; +}; + +struct phandler { + pcap_handler f; + int type; +}; + +static void ip_handle(struct ip *, const u_char *, u_int); +static void sendto_all(struct packet *, int); +static struct node *node_add(struct in_addr *); +static void node_del(struct node *); +static struct node *node_find(struct in_addr *); +static void conn_add(struct in_addr *, int, struct in_addr *, int, int, int); +static void conn_data(struct conn *, int, int); +static void conn_del(struct conn *); +static pcap_handler lookup_phandler(int); +static struct user *finduser(struct sockaddr_in *); + +static void phandler_ether(u_char *, + const struct pcap_pkthdr *, const u_char *); +static void phandler_loop(u_char *, + const struct pcap_pkthdr *, const u_char *); +static void ev_pcap(int, short, void *); +static void ev_timer(int, short, void *); + +static void usrconn(struct imsgev *, struct imsg *); +static void imsgev_main(struct imsgev *, int, struct imsg *); + +static struct phandler phandlers[] = { + { phandler_ether, DLT_EN10MB }, + { phandler_ether, DLT_IEEE802 }, + { phandler_loop, DLT_LOOP }, + { NULL, 0 }, +}; +struct capture *cap; +LIST_HEAD(, user) usr_list; +int usr_count = 0; + +static void +sig_handler(int sig, short why, void *data) +{ + log_info("user: got signal %d", sig); + if (sig == SIGINT || sig == SIGTERM) + event_loopexit(NULL); +} + +int +user_init(int fd[2], pcap_t *pcap) +{ + struct event ev_sigint, ev_sigterm, ev_sigchld, ev_sighup; + int pid, i; + + pid = fork(); + if (pid < 0) + fatal("user fork"); + if (pid > 0) + return pid; + + setproctitle("user"); + event_init(); + close(fd[0]); + + signal_set(&ev_sigint, SIGINT, sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, sig_handler, NULL); + signal_set(&ev_sigchld, SIGCHLD, sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sigchld, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, SIG_IGN); + + cap = xcalloc(1, sizeof(struct capture)); + cap->fd = fd[1]; + imsgev_init(&cap->iev, cap->fd, NULL, &imsgev_main); + for (i=0; iconn_freeids[i] = i; + cap->time = time(NULL); + + cap->pcap = pcap; + cap->pcap_handler = lookup_phandler(pcap_datalink(pcap)); + cap->pcap_tv.tv_sec = 0; + cap->pcap_tv.tv_usec = PCAP_TO; + event_set(&cap->pcap_ev, pcap_fileno(cap->pcap), + EV_READ, ev_pcap, NULL); + + cap->conntimer_tv.tv_sec = CONNTIMER; + cap->conntimer_tv.tv_usec = 0; + evtimer_set(&cap->conntimer_ev, ev_timer, NULL); + if (event_add(&cap->conntimer_ev, &cap->conntimer_tv) == -1) + fatal("user: event_add conntimer failed: %s", strerror(errno)); + + droppriv(); + + log_info("user: entering event loop"); + event_dispatch(); + + log_info("user: exiting"); + exit(0); +} + +/* + * 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 > pend - sizeof(*v)) +#define NOTRECEIVED(v) (wirelen < sizeof(v)) +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 size, proto, close, response; + struct conn *c, *conn; + + if (NOTCAPTURED(ip)) { + log_pinvalid("user: ip truncated"); + 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; + size = len; + 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; + size = len - sizeof(*tcph); + 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; + size = len - sizeof(*udph); + break; + + case IPPROTO_ICMP: + icmp = (struct icmp *)cp; + if (NOTRECEIVED(*icmp)) { + log_pinvalid("user: icmp too small"); + cap->pinvalid++; + return; + } + proto = IPPROTO_ICMP; + size = len - sizeof(*icmp); + break; + + default: + log_warn("user: unknown ip protocol !"); + break; + } + } else { + /* + * if this isn't the first frag, we're missing the + * next level protocol header. + */ + log_tmp("user: got a fragmented ip packet !"); + } + + conn = NULL; + LIST_FOREACH(c, &cap->conn_list, entry) { + if (((c->src->addr.s_addr == src.s_addr && + c->src_port == src_port && + c->dst->addr.s_addr == dst.s_addr && + c->dst_port == dst_port) || + (c->src->addr.s_addr == dst.s_addr && + c->src_port == dst_port && + c->dst->addr.s_addr == src.s_addr && + c->dst_port == src_port)) && + c->proto == proto) { + conn = c; + if (c->src->addr.s_addr == src.s_addr) + response = 0; + else + response = 1; + break; + } + } + + if (conn) { + if (!close) { + conn_data(conn, size, response); + } else { + conn_del(conn); + } + } else { + if (!close) { + conn_add(&src, src_port, &dst, dst_port, proto, size); + } else { + log_warn("user: captured connection close w/o open !"); + } + } +} + +/* XXX all the packets must have data htos */ +static void +sendto_all(struct packet *p, int size) +{ + struct user *usr; + + LIST_FOREACH(usr, &usr_list, entry) { + if (sendto(net_socket, &p, size, 0, + (struct sockaddr *)&usr->addr, sizeof(usr->addr)) == -1) + log_warn("send_to failed: %s", strerror(errno)); + } +} + +static struct node * +node_add(struct in_addr *addr) +{ + struct node *n; + struct imsg_usrdns_req req; + + log_debug("user: node_add"); + + n = xcalloc(1, sizeof(struct node)); + n->addr.s_addr = addr->s_addr; + n->namelen = NODENAME_WAITING; + n->lastseen = cap->time; + LIST_INSERT_HEAD(&cap->node_list, n, entry); + cap->node_count++; + + req.addr.s_addr = addr->s_addr; + imsgev_compose(&cap->iev, IMSG_USRDNS_REQ, 0, 0, -1, + &req, sizeof(req)); + + return n; +} + +static void +node_del(struct node *n) +{ + if (n->used) + fatal("user: trying to remove a used node !"); + log_debug("user: node_del"); + + LIST_REMOVE(n, entry); + free(n->name); + free(n); + cap->node_count--; +} + +static struct node * +node_find(struct in_addr *remote) +{ + struct node *n; + + LIST_FOREACH(n, &cap->node_list, entry) + if (n->addr.s_addr == remote->s_addr) + return n; + return NULL; +} + +static void +conn_add(struct in_addr *src, int src_port, struct in_addr *dst, int dst_port, int proto, int size) +{ + struct packet p; + struct conn *c; + struct node *srcnode; + struct node *dstnode; + int id; + + log_debug("user: conn_add, %x:%d->%x:%d %d [%d]", + src->s_addr, src_port, dst->s_addr, dst_port, proto, size); + if (cap->conn_freeids_ptr == CONN_FREEIDS_COUNT) { + log_warn("user: out of connection identifiers !"); + return; + } + + id = cap->conn_freeids[cap->conn_freeids_ptr]; + cap->conn_freeids_ptr++; + + srcnode = node_find(src); + if (!srcnode) + srcnode = node_add(src); + srcnode->used++; + dstnode = node_find(dst); + if (!dstnode) + dstnode = node_add(dst); + dstnode->used++; + + c = xmalloc(sizeof(struct conn)); + c->id = id; + c->state = CONNSTATE_ESTABLISHED; + c->src = srcnode; + c->src_port = src_port; + c->dst = dstnode; + c->dst_port = dst_port; + c->proto = proto; + c->size = size; + c->lastseen = cap->time; + LIST_INSERT_HEAD(&cap->conn_list, c, entry); + + bzero(&p, sizeof(p)); + p.ver = PACKET_VERSION; + p.type = PACKET_NEWCONN; + p.newconn_id = id; + p.newconn_src = htonl(src->s_addr); + p.newconn_dst = htonl(dst->s_addr); + p.newconn_proto = htons(proto); + p.newconn_size = htons(size << 16); + sendto_all(&p, PACKET_NEWCONN_SIZE); +} + +static void +conn_data(struct conn *c, int size, int response) +{ + struct packet p; + + log_debug("user: conn_data"); + + c->lastseen = cap->time; + + bzero(&p, sizeof(p)); + p.ver = PACKET_VERSION; + p.type = PACKET_DATA; + p.data_connid = c->id; + p.data_size = htons(size << 16 | response); //XXX + sendto_all(&p, PACKET_DATA_SIZE); +} + +static void +conn_del(struct conn *c) +{ + struct packet p; + + log_debug("user: conn_del"); + + if (c->proto == IPPROTO_TCP) { + switch (c->state) { + case CONNSTATE_ESTABLISHED: + c->state = CONNSTATE_TCPFIN; + return; + case CONNSTATE_TCPFIN: + c->state = CONNSTATE_TCPFIN2; + return; + case CONNSTATE_TCPFIN2: + break; + } + } + + bzero(&p, sizeof(p)); + p.ver = PACKET_VERSION; + p.type = PACKET_DELCONN; + p.delconn_id = c->id; + sendto_all(&p, PACKET_DELCONN_SIZE); + + cap->conn_freeids_ptr--; + cap->conn_freeids[cap->conn_freeids_ptr] = c->id; + + c->src->used--; + c->dst->used--; + + LIST_REMOVE(c, entry); + free(c); +} + +static pcap_handler +lookup_phandler(int type) +{ + struct phandler *p; + + for (p = phandlers; p->f; ++p) { + if (type == p->type) + return p->f; + } + fatal("user: unknown data link type 0x%x", type); + /* NOTREACHED */ + return NULL; +} + +static struct user * +finduser(struct sockaddr_in *remote) +{ + struct user *usr; + struct sockaddr_in *u; + + LIST_FOREACH(usr, &usr_list, entry) { + u = &usr->addr; + if (u->sin_addr.s_addr == remote->sin_addr.s_addr && + u->sin_port == remote->sin_port) + return usr; + } + return NULL; +} + +static void +phandler_ether(u_char *user, const struct pcap_pkthdr *h, const u_char *p) +{ + struct ether_header *ep; + struct ip *ip; + u_short ether_type; + const u_char *pend; + u_int len; + + 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_type = ntohs(ep->ether_type); + if (ether_type <= ETHERMTU) + log_tmp("llc packet !"); + else { + switch (ether_type) { + case ETHERTYPE_IP: + log_tmp("ether IP"); + ip = (struct ip *)(ep + sizeof(struct ether_header)); + ip_handle(ip, pend, len); + break; + default: + log_tmp("non ip packet !"); + break; + } + } +} + +static void +phandler_loop(u_char *user, const struct pcap_pkthdr *h, const u_char *p) +{ + struct ip *ip; + struct ether_header *ep; + u_short ether_type; + u_int family; + const u_char *pend; + u_int len; + + 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; + + memcpy((char *)&family, (char *)p, sizeof(family)); + family = ntohl(family); + switch (family) { + case AF_INET: + log_tmp("loop family AF_INET"); + ip = (struct ip *)(p + NULL_HDRLEN); + len -= NULL_HDRLEN; + ip_handle(ip, pend, len); + break; + case AF_LINK: + ep = (struct ether_header *)(p + NULL_HDRLEN); + ether_type = ntohs(ep->ether_type); + if (ether_type <= ETHERMTU) + log_tmp("llc packet !"); + else { + switch (ether_type) { + case ETHERTYPE_IP: + log_tmp("loop family AF_LINK IP"); + ip = (struct ip *)(ep + sizeof(*ep)); + len -= NULL_HDRLEN + sizeof (*ep); + ip_handle(ip, pend, len); + break; + default: + log_tmp("loop non ip packet !"); + break; + } + } + default: + log_tmp("unknown family %x !", family); + break; + } +} + +static void +ev_pcap(int fd, short why, void *data) +{ + log_tmp("ev_pcap"); + pcap_dispatch(cap->pcap, PCAP_COUNT, cap->pcap_handler, NULL); + + /* reschedule */ + if (event_add(&cap->pcap_ev, &cap->pcap_tv) == -1) + fatal("user: event_add pcap failed : %s", strerror(errno)); +} + +static void +ev_timer(int fd, short why, void *data) +{ + struct conn *c; + struct node *n; + int i, to; + + log_tmp("ev_timer"); + cap->time = time(NULL); + + i = 0; + LIST_FOREACH(c, &cap->conn_list, entry) { + 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->time > c->lastseen + to) + conn_del(c); + else + i++; + } + + if (cap->node_count > NODE_MAX_WITHOUT_TIMEOUT) { + LIST_FOREACH(n, &cap->node_list, entry) { + if (n->used == 0 && + cap->time > n->lastseen + NODE_TIMEOUT) + node_del(n); + } + } + + log_tmp("user: ev_timer leaving with %d active connections and %d active nodes", i, cap->node_count); + if (event_add(&cap->conntimer_ev, &cap->conntimer_tv) == -1) + fatal("user: event_add conntimer failed : %s", strerror(errno)); +} + +static void +usrconn(struct imsgev *iev, struct imsg *imsg) +{ + struct imsg_usrconn *req; + struct imsg_usrconn res; + struct user *usr; + + req = imsg->data; + + if (req->deco) { + usr = finduser(&req->addr); + if (!usr) + fatal("user: trying to deco an inexistant user !"); + LIST_REMOVE(usr, entry); + free(usr); + usr_count--; + if (usr_count == 0) + event_del(&cap->pcap_ev); + + return; + } + + usr = xmalloc(sizeof(struct user)); + addrcpy(&usr->addr, &req->addr); + LIST_INSERT_HEAD(&usr_list, usr, entry); + if (usr_count == 0) { + if (event_add(&cap->pcap_ev, &cap->pcap_tv) == -1) + fatal("user: event_add failed : %s", strerror(errno)); + } + usr_count++; + + addrcpy(&res.addr, &usr->addr); + res.ok = 1; + imsgev_compose(&cap->iev, IMSG_USRCONN_RES, 0, 0, -1, + &res, sizeof(res)); +} + +static void +usrdns(struct imsgev *iev, struct imsg *imsg) +{ + struct imsg_usrdns_res *res; + struct node *n; + struct packet p; + + res = imsg->data; + + n = node_find(&res->addr); + if (!n) + fatal("user: received usrdns response for inexistant node !"); + if (n->namelen != NODENAME_WAITING) + fatal("user: received usrdns response for a nonwaiting node!"); + + if (res->name[0] == '\0') { + log_debug("user: resolv for %x failed", res->addr.s_addr); + n->namelen = NODENAME_FAILED; + return; + } + n->namelen = strnlen(res->name, DNSNAME_MAX); + n->name = strndup(res->name, DNSNAME_MAX); + + log_debug("user: sending node name of %x is %s", + n->addr.s_addr, n->name); + bzero(&p, sizeof(p)); + p.ver = PACKET_VERSION; + p.type = PACKET_NAME; + p.name_addr = htonl(n->addr.s_addr); + p.name_len = n->namelen; + strncpy(p.extra, n->name, sizeof(p.extra)); + sendto_all(&p, PACKET_NAME_SIZE + p.name_len); +} + +static void +imsgev_main(struct imsgev *iev, int code, struct imsg *imsg) +{ + switch (code) { + case IMSGEV_IMSG: + log_debug("user: %s got imsg %i on fd %i", + __func__, imsg->hdr.type, iev->ibuf.fd); + switch (imsg->hdr.type) { + case IMSG_USRCONN_REQ: + usrconn(iev, imsg); + break; + case IMSG_USRDNS_RES: + usrdns(iev, imsg); + break; + default: + fatal("user: %s, unexpected imsg %d", + __func__, imsg->hdr.type); + } + break; + case IMSGEV_EREAD: + case IMSGEV_EWRITE: + case IMSGEV_EIMSG: + fatal("imsgev read/write error"); + /* NOTREACHED */ + break; + case IMSGEV_DONE: + event_loopexit(NULL); + /* NOTREACHED */ + break; + } +} + -- cgit v1.2.3-59-g8ed1b