diff --git a/plugins/tuntap/tuntap.c b/plugins/tuntap/tuntap.c new file mode 100644 index 00000000..90db189f --- /dev/null +++ b/plugins/tuntap/tuntap.c @@ -0,0 +1,609 @@ +#include +#include + +extern struct uwsgi_server uwsgi; + +#define UWSGI_TUNTAP_DEVICE "/dev/net/tun" + +/* + + The tuntap router is a non-blocking highly optimized ip router + translating from tuntap device to socket streams. + + It is meant as a replacement for the currently available networking namespaces + approaches. Compared to veth or macvlan it is really simple and allows total control + over the routing subsystem (in addition to a simple customizable firewalling engine) + + Generally you spawn the tuntap router in the Emperor instance + + [uwsgi] + master = true + emperor = /etc/uwsgi + emperor-use-clone = fs,uts,ipc,pid,net + tuntap-router = emperor0 /tmp/tuntap.socket + exec-as-root = ifconfig emperor0 192.168.0.1 netmask 255.255.255.0 up + exec-as-root = iptables -t nat -F + exec-as-root = iptables -t nat -A POSTROUTING -o eth0 -s 192.168.0.0/24 -j MASQUERADE + exec-as-root = echo 1 > /proc/sys/net/ipv4/ip_forward + + vassals will run in new namespaces in which they create a tuntap device attached to + the tuntap router. UNIX sockets are the only way to connect to the tuntap router + after jailing. + + Firewalling it is based on 2 chains (in and out), and each rule is formed by 3 parameters: + The firewall is applied to traffic from the clients to the tuntap device (out) and the opposite (in) + + + The first matching rule stop the chain, if no rule applies, the policy is "allow" + + the following rules allows access from vassals to the internet, but block + vassals intercommunication + + --tuntap-router-firewall-out = allow 192.168.0.0/24 192.168.0.1 + --tuntap-router-firewall-out = deny 192.168.0.0/24 192.168.0.0/24 + --tuntap-router-firewall-out = allow 192.168.0.0/24 0.0.0.0 + --tuntap-router-firewall-out = deny + --tuntap-router-firewall-in = allow 192.168.0.1 192.168.0.0/24 + --tuntap-router-firewall-in = deny 192.168.0.0/24 192.168.0.0/24 + --tuntap-router-firewall-in = allow 0.0.0.0 192.168.0.0/24 + --tuntap-router-firewall-in = deny + + Author: Roberto De Ioris + + TODO: + + some form of security to disallow raw access to the tuntap router unix socket + +*/ + +/* + + a peer is a client connected to the router. It has 2 queue, one for read + and one for write. The write queue can be filled up. If the write queue is full, packets are dropped. + +*/ +struct uwsgi_tuntap_peer { + int fd; + uint32_t addr; + int wait_for_write; + int blocked_read; + size_t written; + char header[4]; + uint8_t header_pos; + char *buf; + uint16_t buf_pktsize; + uint16_t buf_pos; + char *write_buf; + uint16_t write_buf_pktsize; + uint16_t write_buf_pos; + struct uwsgi_tuntap_peer *prev; + struct uwsgi_tuntap_peer *next; +}; + +struct uwsgi_tuntap_firewall_rule { + uint8_t action; + uint32_t src; + uint32_t src_mask; + uint32_t dst; + uint32_t dst_mask; + struct uwsgi_tuntap_firewall_rule *next; +}; + +struct uwsgi_tuntap { + char *addr; + char *device; + int fd; + int server_fd; + int queue; + uint16_t buffer_size; + char *buf; + char *write_buf; + uint16_t write_pktsize; + uint16_t write_pos; + int wait_for_write; + struct uwsgi_tuntap_peer *peers_head; + struct uwsgi_tuntap_peer *peers_tail; + struct uwsgi_tuntap_firewall_rule *fw_in; + struct uwsgi_tuntap_firewall_rule *fw_out; +} utt; + +static struct uwsgi_option uwsgi_tuntap_options[] = { + {"tuntap-router", required_argument, 0, "run the tuntap router (syntax: )", uwsgi_opt_set_str, &utt.addr, 0}, + {"tuntap-device", required_argument, 0, "add a tuntap device to the instance (syntax: [ ])", uwsgi_opt_set_str, &utt.device, 0}, + {NULL, 0, 0, NULL, NULL, NULL, 0}, +}; + +// create a new peer +static struct uwsgi_tuntap_peer *uwsgi_tuntap_peer_create(int fd) { + + struct uwsgi_tuntap_peer *uttp = uwsgi_calloc(sizeof(struct uwsgi_tuntap_peer)); + uttp->fd = fd; + uttp->buf = uwsgi_malloc(utt.buffer_size + 4); + uttp->write_buf = uwsgi_malloc(utt.buffer_size + 4); + + if (utt.peers_tail) { + utt.peers_tail->next = uttp; + uttp->prev = utt.peers_tail; + utt.peers_tail = uttp; + } + else { + utt.peers_head = uttp; + utt.peers_tail = uttp; + } + + return uttp; +} + +// destroy a peer +static void uwsgi_tuntap_peer_destroy(struct uwsgi_tuntap_peer *uttp) { + struct uwsgi_tuntap_peer *prev = uttp->prev; + struct uwsgi_tuntap_peer *next = uttp->next; + + if (prev) { + prev->next = next; + } + + if (next) { + next->prev = prev; + } + + if (uttp == utt.peers_head) { + utt.peers_head = next; + } + + if (uttp == utt.peers_tail) { + utt.peers_tail = prev; + } + + free(uttp->buf); + free(uttp); +} + + +// get a peer by addr +static struct uwsgi_tuntap_peer *uwsgi_tuntap_peer_get_by_addr(uint32_t addr) { + struct uwsgi_tuntap_peer *uttp = utt.peers_head; + while (uttp) { + if (uttp->addr == addr) + return uttp; + uttp = uttp->next; + } + + return NULL; +} + +// block all reading peers +static void uwsgi_tuntap_block_reads() { + struct uwsgi_tuntap_peer *uttp = utt.peers_head; + while (uttp) { + if (!uttp->wait_for_write) { + if (!uttp->blocked_read) { + if (event_queue_del_fd(utt.queue, uttp->fd, event_queue_read())) { + struct uwsgi_tuntap_peer *tmp_uttp = uttp; + uttp = uttp->next; + uwsgi_tuntap_peer_destroy(tmp_uttp); + continue; + } + uttp->blocked_read = 1; + } + } + uttp = uttp->next; + } +} + +//unblock all reading peers +static void uwsgi_tuntap_unblock_reads() { + struct uwsgi_tuntap_peer *uttp = utt.peers_head; + while (uttp) { + if (uttp->blocked_read) { + if (event_queue_add_fd_read(utt.queue, uttp->fd)) { + struct uwsgi_tuntap_peer *tmp_uttp = uttp; + uttp = uttp->next; + uwsgi_tuntap_peer_destroy(tmp_uttp); + continue; + } + uttp->blocked_read = 0; + } + uttp = uttp->next; + } +} + +// enqueue a packet in the tuntap device +static void uwsgi_tuntap_enqueue() { + ssize_t rlen = write(utt.fd, utt.write_buf + utt.write_pos, utt.write_pktsize - utt.write_pos); + // error on the tuntap device, destroy !!! + if (rlen == 0) { + uwsgi_error("uwsgi_tuntap_enqueue()/write()"); + exit(1); + } + + if (rlen < 0) { + if (uwsgi_is_again()) + goto retry; + uwsgi_error("uwsgi_tuntap_enqueue()/write()"); + exit(1); + } + + utt.write_pos += rlen; + if (utt.write_pos >= utt.write_pktsize) { + utt.write_pos = 0; + if (utt.wait_for_write) { + if (event_queue_fd_write_to_read(utt.queue, utt.fd)) { + uwsgi_error("uwsgi_tuntap_enqueue()/event_queue_fd_read_to_write()"); + exit(1); + } + utt.wait_for_write = 0; + } + uwsgi_tuntap_unblock_reads(); + return; + } + +retry: + if (!utt.wait_for_write) { + uwsgi_tuntap_block_reads(); + if (event_queue_fd_read_to_write(utt.queue, utt.fd)) { + uwsgi_error("uwsgi_tuntap_enqueue()/event_queue_fd_read_to_write()"); + exit(1); + } + utt.wait_for_write = 1; + } +} + +// receive a packet from the client +static int uwsgi_tuntap_peer_dequeue(struct uwsgi_tuntap_peer *uttp) { + // get body + if (uttp->header_pos >= 4) { + ssize_t rlen = read(uttp->fd, uttp->buf + uttp->buf_pos, uttp->buf_pktsize - uttp->buf_pos); + if (rlen == 0) + return -1; + if (rlen < 0) { + if (uwsgi_is_again()) + return 0; + uwsgi_error("uwsgi_tuntap_peer_dequeue()/read()"); + return -1; + } + uttp->buf_pos += rlen; + // a whole pkt has been received + if (uttp->buf_pos >= uttp->buf_pktsize) { + // if there is no associated address store the source + if (!uttp->addr) { + uint32_t *src_ip = (uint32_t *) & uttp->buf[12]; + uttp->addr = *src_ip; + // drop invalid ip addresses + if (!uttp->addr) + return -1; + char ip[INET_ADDRSTRLEN + 1]; + memset(ip, 0, INET_ADDRSTRLEN + 1); + if (!inet_ntop(AF_INET, &uttp->addr, ip, INET_ADDRSTRLEN)) { + uwsgi_error("inet_ntop()"); + return -1; + } + uwsgi_log("[tuntap-router] registered new peer %s (fd: %d)\n", ip, uttp->fd); + } + memcpy(utt.write_buf, uttp->buf, uttp->buf_pktsize); + utt.write_pktsize = uttp->buf_pktsize; + uttp->header_pos = 0; + uttp->buf_pos = 0; + uwsgi_tuntap_enqueue(); + } + return 0; + } + ssize_t rlen = read(uttp->fd, uttp->header + uttp->header_pos, 4 - uttp->header_pos); + if (rlen == 0) + return -1; + if (rlen < 0) { + if (uwsgi_is_again()) + return 0; + uwsgi_error("uwsgi_tuntap_peer_dequeue()/read()"); + return -1; + } + uttp->header_pos += rlen; + if (uttp->header_pos >= 4) { + uint16_t *pktsize = (uint16_t *) &uttp->header[1]; + uttp->buf_pktsize = *pktsize; + } + return 0; +} + +// enqueue a packet to the client +static int uwsgi_tuntap_peer_enqueue(struct uwsgi_tuntap_peer *uttp) { + + ssize_t rlen = write(uttp->fd, uttp->write_buf + uttp->written, uttp->write_buf_pktsize - uttp->written); + if (rlen == 0) { + uwsgi_error("uwsgi_tuntap_peer_enqueue()/write()"); + return -1; + } + + if (rlen < 0) { + if (uwsgi_is_again()) + goto retry; + uwsgi_error("uwsgi_tuntap_peer_enqueue()/write()"); + return -1; + } + + uttp->written += rlen; + if (uttp->written >= uttp->write_buf_pktsize) { + uttp->written = 0; + uttp->write_buf_pktsize = 0; + if (uttp->wait_for_write) { + // if the write ends while we are writing to the tuntap, block the reads + if (utt.wait_for_write) { + uttp->blocked_read = 1; + } + else { + if (event_queue_fd_write_to_read(utt.queue, uttp->fd)) { + uwsgi_error("uwsgi_tuntap_peer_enqueue()/event_queue_fd_write_to_read()"); + return -1; + } + } + utt.wait_for_write = 0; + } + return 0; + } + + memmove(uttp->write_buf, uttp->write_buf + rlen, uttp->write_buf_pktsize - rlen); + uttp->write_buf_pktsize -= rlen; + +retry: + if (!uttp->wait_for_write) { + if (event_queue_fd_read_to_write(utt.queue, uttp->fd)) { + uwsgi_error("uwsgi_tuntap_peer_enqueue()/event_queue_fd_read_to_write()"); + return -1; + } + uttp->wait_for_write = 1; + } + + return 0; +} + + +static void *uwsgi_tuntap_loop(void *arg) { + + // block signals on this thread + sigset_t smask; + sigfillset(&smask); +#ifndef UWSGI_DEBUG + sigdelset(&smask, SIGSEGV); +#endif + pthread_sigmask(SIG_BLOCK, &smask, NULL); + + int fd = utt.fd; + + uwsgi_socket_nb(fd); + + if (!utt.buffer_size) + utt.buffer_size = 8192; + utt.buf = uwsgi_malloc(utt.buffer_size); + utt.write_buf = uwsgi_malloc(utt.buffer_size); + + utt.queue = event_queue_init(); + + event_queue_add_fd_read(utt.queue, fd); + int server_fd = uwsgi_connect("/tmp/tuntap.socket", 30, 0); + if (event_queue_add_fd_read(utt.queue, server_fd)) { + // retry; + } + + struct uwsgi_tuntap_peer *uttp = uwsgi_tuntap_peer_create(server_fd); + + for (;;) { + int interesting_fd = -1; + int ret = event_queue_wait(utt.queue, -1, &interesting_fd); + + if (ret <= 0) + continue; + + if (interesting_fd == fd) { + if (utt.wait_for_write) { + uwsgi_tuntap_enqueue(); + continue; + } + ssize_t rlen = read(fd, utt.buf, utt.buffer_size); + if (rlen <= 0) { + uwsgi_error("uwsgi_tuntap_loop()/read()"); + exit(1); + } + uint16_t pktsize = rlen; + char *ptr = uttp->write_buf + uttp->write_buf_pktsize; + memcpy(ptr + 4, utt.buf, rlen); + ptr[0] = 0; + ptr[1] = (uint8_t) (pktsize & 0xff); + ptr[2] = (uint8_t) ((pktsize >> 8) & 0xff); + ptr[3] = 0; + uttp->write_buf_pktsize+= pktsize+4; + if (uwsgi_tuntap_peer_enqueue(uttp)) { + uwsgi_log("server disconnected...\n"); + exit(1); + } + continue; + } + + + if (interesting_fd == server_fd) { + // read from the client + if (!uttp->wait_for_write) { + if (uwsgi_tuntap_peer_dequeue(uttp)) { + uwsgi_log("server disconnected...\n"); + exit(1); + } + } + else { + // something is wrong (the tuntap device is blocked) + if (utt.wait_for_write) + continue; + + // write to the client + if (uwsgi_tuntap_peer_enqueue(uttp)) { + uwsgi_log("server disconnected...\n"); + exit(1); + } + } + } + } + + return NULL; +} + +static void uwsgi_tuntap_client() { + + if (!utt.device) return; + + struct ifreq ifr; + utt.fd = open(UWSGI_TUNTAP_DEVICE, O_RDWR); + if (utt.fd < 0) { + uwsgi_error_open(UWSGI_TUNTAP_DEVICE); + exit(1); + } + + memset(&ifr, 0, sizeof(struct ifreq)); + + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + strncpy(ifr.ifr_name, utt.device, IFNAMSIZ); + + if (ioctl(utt.fd, TUNSETIFF, (void *) &ifr) < 0) { + uwsgi_error("uwsgi_tuntap_init()/ioctl()"); + exit(1); + } + + uwsgi_log("initialized tuntap device %s\n", ifr.ifr_name); + + pthread_t t; + pthread_create(&t, NULL, uwsgi_tuntap_loop, NULL); +} + +void uwsgi_tuntap_router_loop(int id, void *foobar) { + int i; + utt.buf = uwsgi_malloc(utt.buffer_size); + utt.write_buf = uwsgi_malloc(utt.buffer_size); + utt.queue = event_queue_init(); + void *events = event_queue_alloc(64); + if (event_queue_add_fd_read(utt.queue, utt.server_fd)) + exit(1); + if (event_queue_add_fd_read(utt.queue, utt.fd)) + exit(1); + for (;;) { + int nevents = event_queue_wait_multi(utt.queue, -1, events, 64); + for (i = 0; i < nevents; i++) { + int interesting_fd = event_queue_interesting_fd(events, i); + if (interesting_fd == utt.fd) { + // if writing, continue enqueuing + if (utt.wait_for_write) { + uwsgi_tuntap_enqueue(); + continue; + } + ssize_t rlen = read(utt.fd, utt.buf, utt.buffer_size); + if (rlen <= 0) { + uwsgi_error("uwsgi_tuntap_router_loop()/read()"); + exit(1); + } + uint32_t *dst_ip = (uint32_t *) & utt.buf[16]; + struct uwsgi_tuntap_peer *uttp = uwsgi_tuntap_peer_get_by_addr(*dst_ip); + if (!uttp) + continue; + uint16_t pktsize = rlen; + char *ptr = uttp->write_buf + uttp->write_buf_pktsize; + memcpy(ptr + 4, utt.buf, rlen); + ptr[0] = 0; + ptr[1] = (uint8_t) (pktsize & 0xff); + ptr[2] = (uint8_t) ((pktsize >> 8) & 0xff); + ptr[3] = 0; + uttp->write_buf_pktsize+= pktsize+4; + if (uwsgi_tuntap_peer_enqueue(uttp)) { + uwsgi_tuntap_peer_destroy(uttp); + } + continue; + } + + if (interesting_fd == utt.server_fd) { + int client_fd = uwsgi_accept(utt.server_fd); + if (client_fd < 0) { + uwsgi_error("uwsgi_tuntap_server_loop()/accept()"); + continue; + } + struct uwsgi_tuntap_peer *uttp = uwsgi_tuntap_peer_create(client_fd); + if (event_queue_add_fd_read(utt.queue, uttp->fd)) { + uwsgi_tuntap_peer_destroy(uttp); + } + continue; + } + + struct uwsgi_tuntap_peer *uttp = utt.peers_head; + while (uttp) { + if (interesting_fd == uttp->fd) { + // read from the client + if (!uttp->wait_for_write) { + if (uwsgi_tuntap_peer_dequeue(uttp)) { + struct uwsgi_tuntap_peer *tmp_uttp = uttp; + uttp = uttp->next; + uwsgi_tuntap_peer_destroy(tmp_uttp); + continue; + } + } + else { + // something is wrong (the tuntap device is blocked) + if (utt.wait_for_write) + break; + + // write to the client + if (uwsgi_tuntap_peer_enqueue(uttp)) { + struct uwsgi_tuntap_peer *tmp_uttp = uttp; + uttp = uttp->next; + uwsgi_tuntap_peer_destroy(tmp_uttp); + continue; + } + } + break; + } + uttp = uttp->next; + } + } + } +} + +static void uwsgi_tuntap_router() { + + if (!utt.addr) return; + + if (!utt.buffer_size) + utt.buffer_size = 8192; + + char *space = strchr(utt.addr, ' '); + if (!space) { + uwsgi_log("invalid tuntap router syntax, must be \n"); + exit(1); + } + + utt.server_fd = bind_to_unix(space+1, uwsgi.listen_queue, uwsgi.chmod_socket, uwsgi.abstract_socket); + + struct ifreq ifr; + utt.fd = open(UWSGI_TUNTAP_DEVICE, O_RDWR); + if (utt.fd < 0) { + uwsgi_error_open(UWSGI_TUNTAP_DEVICE); + exit(1); + } + + memset(&ifr, 0, sizeof(struct ifreq)); + + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + *space = 0 ; + strncpy(ifr.ifr_name, utt.addr, IFNAMSIZ); + *space = ' '; + + if (ioctl(utt.fd, TUNSETIFF, (void *) &ifr) < 0) { + uwsgi_error("uwsgi_tuntap_server()/ioctl()"); + exit(1); + } + + uwsgi_log("initialized tuntap device %s\n", ifr.ifr_name); + if (register_gateway("uWSGI tuntap router", uwsgi_tuntap_router_loop, NULL) == NULL) { + uwsgi_log("unable to register the tuntap server gateway\n"); + exit(1); + } +} + +struct uwsgi_plugin tuntap_plugin = { + .name = "tuntap", + .options = uwsgi_tuntap_options, + .post_jail = uwsgi_tuntap_client, + .jail = uwsgi_tuntap_router, +}; diff --git a/plugins/tuntap/uwsgiplugin.py b/plugins/tuntap/uwsgiplugin.py new file mode 100644 index 00000000..187bf7ea --- /dev/null +++ b/plugins/tuntap/uwsgiplugin.py @@ -0,0 +1,5 @@ +NAME='tuntap' +CFLAGS=[] +LDFLAGS=[] +LIBS=[] +GCC_LIST=['tuntap']