#include #include #include #include #include #include #include #include #include #include #include #include #include #include "libglouglou.h" int server_send(struct gg_server *, struct gg_user *, void *, int); void cb_srv_receive(evutil_socket_t, short, void *); int user_send(struct gg_user *, void *, int); struct gg_user *user_add(struct gg_server *, struct sockaddr_in *); void user_del(struct gg_server *, struct gg_user *); struct gg_user * user_find(struct gg_server *, struct sockaddr_in *); int client_send(struct gg_client *, void *, int); void cb_cli_receive(evutil_socket_t, short, void *); void cb_cli_timer(evutil_socket_t, short, void *); struct gg_packet *pkt_decode(char **, int *); int pkt_getsize(struct gg_packet *); /* * Server */ /* * start a server */ struct gg_server * gg_server_start(struct event_base *ev_base, char *ip, int port, int (*handle_conn)(struct gg_server *, struct gg_user *), int (*handle_packet)(struct gg_server *, struct gg_user *, struct gg_packet *), void *usrdata) { struct gg_server *srv; struct sockaddr_in sock_addr; struct event *ev; int s; int sock_on = 1; srv = xcalloc(1, sizeof(struct gg_server)); srv->ev_base = ev_base; srv->ip = ip; srv->port = port; srv->handle_conn = handle_conn; srv->handle_packet = handle_packet; srv->usrdata = usrdata; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) goto err; srv->sock = s; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &sock_on, sizeof(sock_on)); fd_nonblock(s); bzero(&sock_addr, sizeof(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_addr.s_addr = inet_addr(ip); sock_addr.sin_port = htons(port); addrcpy(&srv->addr, &sock_addr); if (bind(s, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) != 0) goto err; ev = event_new(ev_base, s, EV_READ|EV_PERSIST, cb_srv_receive, srv); event_add(ev, NULL); srv->ev = ev; return srv; err: printf("libglouglou: gg_server_start: Error ! %s", strerror(errno)); gg_server_stop(srv); return NULL; } int gg_server_send(struct gg_server *srv, struct gg_packet *pkt, struct gg_user *usr) { int size; size = pkt_getsize(pkt); return server_send(srv, usr, pkt, size); } void gg_server_stop(struct gg_server *srv) { if (srv->sock) { server_send(srv, NULL, "", 0); close(srv->sock); } if (srv->ev) event_del(srv->ev); free(srv); } /* * Server - private */ int server_send(struct gg_server *srv, struct gg_user *usr, void *data, int size) { struct gg_user *u; if (usr) return user_send(usr, data, size); else LIST_FOREACH(u, &srv->user_list, entry) user_send(u, data, size); /* XXX return error if at least one error ? */ return 0; } void cb_srv_receive(evutil_socket_t fd, short what, void *arg) { struct gg_server *srv; struct gg_user *usr; struct gg_packet *pkt; struct sockaddr_in remote; socklen_t remote_len; char buf[PACKET_BUFFER_SIZE]; char *buf_p; int buf_len; int len; srv = arg; remote_len = sizeof(struct sockaddr_in); len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&remote, &remote_len); if (len < 0) { printf("libglouglou: cb_srv_receive: recvfrom failed\n"); return; } usr = user_find(srv, &remote); if (!usr) { usr = user_add(srv, &remote); if (srv->handle_conn) srv->handle_conn(srv, usr); if (sendto(srv->sock, "", strlen(""), 0, (struct sockaddr *)&remote, remote_len) == -1) printf("srv: send failed: %s\n", strerror(errno)); } else { printf("srv: Incoming data from existing user !\n"); if (len == 0) { printf("libglouglou: cb_srv_receive: recvfrom = 0\n"); user_del(srv, usr); return; } buf_p = buf; buf_len = len; while (buf_len > 0 && (pkt = pkt_decode(&buf_p, &buf_len))) if (srv->handle_packet) srv->handle_packet(srv, usr, pkt); if (buf_len > 0) { /* XXX store incomplete packet for next recv */ printf("srv: incomplete packet, dropped %d bytes !\n", buf_len); } } } struct gg_user * user_add(struct gg_server *srv, struct sockaddr_in *remote) { struct gg_user *usr; usr = xcalloc(1, sizeof(struct gg_user)); usr->id = srv->user_id_count; srv->user_id_count++; usr->sock = srv->sock; addrcpy(&usr->addr, remote); LIST_INSERT_HEAD(&srv->user_list, usr, entry); printf("srv: Add user %d !\n", usr->id); return usr; } void user_del(struct gg_server *srv, struct gg_user *usr) { printf("srv: Del user %d !\n", usr->id); LIST_REMOVE(usr, entry); free(usr); } struct gg_user * user_find(struct gg_server *srv, struct sockaddr_in *remote) { struct gg_user *usr; struct sockaddr_in *u; LIST_FOREACH(usr, &srv->user_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; } int user_send(struct gg_user *usr, void *data, int size) { if (sendto(usr->sock, data, size, 0, (struct sockaddr *)&usr->addr, sizeof(struct sockaddr_in)) == -1) { printf("user_send failed: %s", strerror(errno)); return -1; } return 0; } /* * Client */ /* * connect to a server */ struct gg_client * gg_client_connect(struct event_base *ev_base, char *ip, int port, int (*handle_conn)(struct gg_client *), int (*handle_packet)(struct gg_client *, struct gg_packet *), void *usrdata) { struct gg_client *cli; struct sockaddr_in sock_addr; struct event *ev; struct timeval tv; int s; int sock_on = 1; cli = xcalloc(1, sizeof(struct gg_client)); cli->ev_base = ev_base; cli->ip = ip; cli->port = port; cli->handle_conn = handle_conn; cli->handle_packet = handle_packet; cli->usrdata = usrdata; cli->status = GG_CLIENT_STATUS_CONNECTING; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) goto err; cli->sock = s; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &sock_on, sizeof(sock_on)); fd_nonblock(s); bzero(&sock_addr, sizeof(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_addr.s_addr=inet_addr(ip); sock_addr.sin_port=htons(port); addrcpy(&cli->addr, &sock_addr); ev = event_new(ev_base, s, EV_READ|EV_PERSIST, cb_cli_receive, cli); event_add(ev, NULL); cli->ev = ev; ev = evtimer_new(ev_base, cb_cli_timer, cli); cli->ev_timer = ev; bzero(&tv, sizeof(struct timeval)); tv.tv_sec = 0; evtimer_add(ev, &tv); return cli; err: printf("libglouglou: gg_client_connect: Error ! %s", strerror(errno)); gg_client_disconnect(cli); return NULL; } int gg_client_send(struct gg_client *cli, struct gg_packet *pkt) { int size; size = pkt_getsize(pkt); return client_send(cli, pkt, size); } void gg_client_disconnect(struct gg_client *cli) { if (cli->sock) { client_send(cli, "", 0); close(cli->sock); } if (cli->ev) event_del(cli->ev); if (cli->ev_timer) event_del(cli->ev_timer); free(cli); } /* * Client - private */ int client_send(struct gg_client *cli, void *data, int size) { if (sendto(cli->sock, data, size, 0, (struct sockaddr *)&cli->addr, sizeof(struct sockaddr_in)) == -1) { printf("gg_client_send failed: %s", strerror(errno)); return -1; } return 0; } void cb_cli_receive(evutil_socket_t fd, short what, void *arg) { struct gg_client *cli; struct gg_packet *pkt; struct sockaddr_in remote; socklen_t remote_len; char buf[PACKET_BUFFER_SIZE]; char *buf_p; int buf_len; int len; cli = arg; remote_len = sizeof(struct sockaddr_in); len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&remote, &remote_len); if (addrcmp(&cli->addr, &remote)) { printf("libglouglou; cb_cli_receive: receiving from stranger !\n"); return; } if (len < 0) { printf("libglouglou: cb_cli_receive: recvfrom failed\n"); return; } switch (cli->status) { case GG_CLIENT_STATUS_CONNECTING: printf("cli: Connected !\n"); cli->status = GG_CLIENT_STATUS_CONNECTED; if (cli->handle_conn) cli->handle_conn(cli); break; case GG_CLIENT_STATUS_CONNECTED: if (len == 0) { printf("libglouglou: cb_cli_receive: recvfrom = 0\n"); cli->status = GG_CLIENT_STATUS_CONNECTING; return; } printf("cli: Incoming data !\n"); buf_p = buf; buf_len = len; while (buf_len > 0 && (pkt = pkt_decode(&buf_p, &buf_len))) if (cli->handle_packet) cli->handle_packet(cli, pkt); if (buf_len > 0) { /* XXX store incomplete packet for next recv */ printf("cli: incomplete packet, dropped %d bytes !\n", buf_len); } break; } } void cb_cli_timer(evutil_socket_t fd, short what, void *arg) { struct timeval tv; struct gg_client *cli; cli = arg; switch (cli->status) { case GG_CLIENT_STATUS_CONNECTING: client_send(cli, "", 0); break; case GG_CLIENT_STATUS_CONNECTED: // XXX send keepalive break; } bzero(&tv, sizeof(struct timeval)); tv.tv_sec = 2; event_add(cli->ev_timer, &tv); } /* * Packets - private */ struct gg_packet * pkt_decode(char **buf, int *buf_len) { struct gg_packet *pkt, *newpkt = NULL; int len; int packet_len; len = *buf_len; if (len < PACKET_HEADER_SIZE) goto incomplete; if (len > PACKET_BUFFER_SIZE) goto invalid; pkt = (struct gg_packet *)*buf; if (pkt->ver != PACKET_VERSION) goto invalid; if (pkt->type < PACKET_TYPE_MIN || pkt->type > PACKET_TYPE_MAX) goto invalid; newpkt = xmalloc(sizeof(struct gg_packet)); newpkt->ver = pkt->ver; newpkt->type = pkt->type; switch(pkt->type) { case PACKET_NEWCONN: packet_len = PACKET_NEWCONN_SIZE; if (len < packet_len) goto invalid; newpkt->newconn_id = pkt->newconn_id; newpkt->newconn_src = ntohl(pkt->newconn_src); newpkt->newconn_dst = ntohl(pkt->newconn_dst); newpkt->newconn_proto = pkt->newconn_proto; newpkt->newconn_size = ntohs(pkt->newconn_size); break; case PACKET_DELCONN: packet_len = PACKET_NEWCONN_SIZE; if (len < packet_len) goto invalid; newpkt->delconn_id = pkt->delconn_id; break; case PACKET_DATA: packet_len = PACKET_NEWCONN_SIZE; if (len < packet_len) goto invalid; newpkt->data_connid = pkt->data_connid; newpkt->data_size = ntohs(pkt->data_size); break; case PACKET_NAME: packet_len = PACKET_NEWCONN_SIZE; if (len < packet_len) goto invalid; if (len < packet_len + pkt->name_len) goto invalid; if (strnlen((char *)pkt->name_fqdn, len) != pkt->name_len) goto invalid; newpkt->name_addr = ntohl(pkt->name_addr); newpkt->name_len = ntohs(pkt->name_len); strncpy((char *)newpkt->name_fqdn, (char *)pkt->name_fqdn, pkt->name_len); break; case PACKET_PROC_FORK: packet_len = PACKET_PROC_FORK_SIZE; if (len < packet_len) goto invalid; newpkt->proc_pid = ntohl(pkt->proc_pid); newpkt->proc_fork_ppid = ntohl(pkt->proc_fork_ppid); newpkt->proc_fork_cpid = ntohl(pkt->proc_fork_cpid); newpkt->proc_fork_tgid = ntohl(pkt->proc_fork_tgid); break; case PACKET_PROC_EXEC: packet_len = PACKET_PROC_EXEC_SIZE; if (len < packet_len) goto invalid; if (len < packet_len + pkt->proc_exec_cmdlen) goto invalid; if (strnlen((char *)pkt->proc_exec_cmd, len) != pkt->proc_exec_cmdlen) goto invalid; newpkt->proc_pid = ntohl(pkt->proc_pid); newpkt->proc_exec_cmdlen = ntohs(pkt->proc_exec_cmdlen); strncpy((char *)newpkt->proc_exec_cmd, (char *)pkt->proc_exec_cmd, pkt->proc_exec_cmdlen); break; default: goto invalid; } *buf = *buf + packet_len; *buf_len = len - packet_len; return newpkt; incomplete: printf("pkt_decode: incomplete packet\n"); if (newpkt) free(newpkt); *buf_len = len; return NULL; invalid: printf("pkt_decode: invalid packet\n"); if (newpkt) free(newpkt); *buf = NULL; *buf_len = 0; return NULL; } /* get the size of a packet before sending it on the wire * assumes that the packet is trusted (we can do strlen on pkt->name_fqdn...) */ int pkt_getsize(struct gg_packet *pkt) { int size; switch(pkt->type) { case PACKET_NEWCONN: size = PACKET_NEWCONN_SIZE; break; case PACKET_DELCONN: size = PACKET_DELCONN_SIZE; break; case PACKET_DATA: size = PACKET_DATA_SIZE; break; case PACKET_NAME: size = PACKET_NAME_SIZE + strnlen((char *)pkt->name_fqdn, GG_PKTARG_MAX); break; case PACKET_PROC_FORK: size = PACKET_PROC_FORK_SIZE; break; case PACKET_PROC_EXEC: size = PACKET_PROC_EXEC_SIZE + strnlen((char *)pkt->proc_exec_cmd, GG_PKTARG_MAX); break; default: size = 0; } return size; } /* * Utils */ void * xmalloc(size_t size) { void *data; data = malloc(size); if (!data) err(1, "could not malloc %d", (int)size); return data; } void * xcalloc(size_t nmemb, size_t size) { void *data; data = calloc(nmemb, size); if (!data) err(1, "could not calloc %d", (int)size); return data; } void fd_nonblock(int fd) { int flags = fcntl(fd, F_GETFL, 0); int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK); if (rc == -1) printf("failed to set fd %i non-blocking", fd); } void addrcpy(struct sockaddr_in *dst, struct sockaddr_in *src) { dst->sin_addr.s_addr = src->sin_addr.s_addr; dst->sin_port = src->sin_port; dst->sin_family = src->sin_family; } int addrcmp(struct sockaddr_in *a, struct sockaddr_in *b) { if (a->sin_addr.s_addr != b->sin_addr.s_addr) return -1; if (a->sin_port != b->sin_port) return -2; if (a->sin_family != b->sin_family) return -3; return 0; }