diff --git a/Makefile b/Makefile index e5942dc..bec0d11 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ CFLAGS += -Wall -Wextra -pedantic all: merd qrap -merd: merd.c merd.h - $(CC) $(CFLAGS) merd.c -o merd +merd: merd.c merd.h arp.c arp.h dhcp.c dhcp.h util.c util.h + $(CC) $(CFLAGS) merd.c arp.c dhcp.c util.c -o merd qrap: qrap.c merd.h $(CC) $(CFLAGS) qrap.o -o qrap diff --git a/arp.c b/arp.c new file mode 100644 index 0000000..1a38912 --- /dev/null +++ b/arp.c @@ -0,0 +1,82 @@ +/* MERD - MacVTap Egress and Routing Daemon + * + * arp.c - ARP implementation + * + * Author: Stefano Brivio + * License: GPLv2 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "merd.h" +#include "dhcp.h" +#include "util.h" + +/** + * struct arpmsg - 802.2 ARP IPv4 payload + * @sha: Sender hardware address + * @sip: Sender IP address + * @tha: Target hardware address + * @tip: Target IP address + */ +struct arpmsg { + unsigned char sha[ETH_ALEN]; + unsigned char sip[4]; + unsigned char tha[ETH_ALEN]; + unsigned char tip[4]; +} __attribute__((__packed__)); + +/** + * dhcp() - Check if this is an ARP message, reply as needed + * @c: Execution context + * @len: Total L2 packet length + * @eh: Packet buffer, Ethernet header + * + * Return: 0 if it's not an ARP message, 1 if handled, -1 on failure + */ +int arp(struct ctx *c, unsigned len, struct ethhdr *eh) +{ + struct arphdr *ah = (struct arphdr *)(eh + 1); + struct arpmsg *am = (struct arpmsg *)(ah + 1); + unsigned char swap[4]; + + if (eh->h_proto != htons(ETH_P_ARP)) + return 0; + + if (len < sizeof(*eh) + sizeof(*ah) + sizeof(*am)) + return -1; + + if (ah->ar_hrd != htons(ARPHRD_ETHER) || + ah->ar_pro != htons(ETH_P_IP) || + ah->ar_hln != ETH_ALEN || ah->ar_pln != 4 || + ah->ar_op != htons(ARPOP_REQUEST)) + return 1; + + ah->ar_op = htons(ARPOP_REPLY); + memcpy(am->tha, am->sha, ETH_ALEN); + memcpy(am->sha, c->mac, ETH_ALEN); + + memcpy(swap, am->tip, 4); + memcpy(am->tip, am->sip, 4); + memcpy(am->sip, swap, 4); + + len = sizeof(*eh) + sizeof(*ah) + sizeof(*am); + memcpy(eh->h_dest, eh->h_source, ETH_ALEN); + memcpy(eh->h_source, c->mac, ETH_ALEN); + + if (send(c->fd_unix, eh, len, 0) < 0) + perror("ARP: send"); + + return 1; +} diff --git a/arp.h b/arp.h new file mode 100644 index 0000000..ef3bd19 --- /dev/null +++ b/arp.h @@ -0,0 +1 @@ +int arp(struct ctx *c, unsigned len, struct ethhdr *eh); diff --git a/dhcp.c b/dhcp.c new file mode 100644 index 0000000..4be3615 --- /dev/null +++ b/dhcp.c @@ -0,0 +1,218 @@ +/* MERD - MacVTap Egress and Routing Daemon + * + * dhcp.c - Minimalistic DHCP server for MERD + * + * Author: Stefano Brivio + * License: GPLv2 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "merd.h" +#include "dhcp.h" +#include "util.h" + +/** + * struct opt - DHCP option + * @force: Force sending, even if the client didn't request it + * @sent: Convenience flag, set while filling replies + * @slen: Length of option defined for server + * @s: Option payload from server + * @clen: Length of option received from client + * @c: Option payload from client + */ +struct opt { + int sent; + int slen; + unsigned char s[255]; + int clen; + unsigned char c[255]; +}; + +static struct opt opts[255] = { + [1] = { 0, 4, { 0 }, 0, { 0 }, }, /* Subnet mask */ + [3] = { 0, 4, { 0 }, 0, { 0 }, }, /* Router */ + [6] = { 0, 4, { 0 }, 0, { 0 }, }, /* DNS */ + [51] = { 0, 4, { 60 }, 0, { 0 }, }, /* Lease time */ + [53] = { 0, 1, { 0 }, 0, { 0 }, }, /* Message type */ +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 +#define DHCPFORCERENEW 9 + [54] = { 0, 4, { 0 }, 0, { 0 }, }, /* Server ID */ +}; + +/** + * struct msg - BOOTP/DHCP message + * @op: BOOTP message type + * @htype: Hardware address type + * @hlen: Hardware address length + * @hops: DHCP relay hops + * @xid: Transaction ID randomly chosen by client + * @secs: Seconds elapsed since beginning of acquisition or renewal + * @flags: DHCP message flags + * @ciaddr: Client IP address in BOUND, RENEW, REBINDING + * @yiaddr: IP address being offered or assigned + * @siaddr: Next server to use in bootstrap + * @giaddr: Relay agent IP address + * @chaddr: Client hardware address + * @sname: Server host name + * @file: Boot file name + * @magic: Magic cookie prefix before options + * @o: Options + */ +struct msg { + uint8_t op; +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t magic; + uint8_t o[308]; +} __attribute__((__packed__)); + +/** + * fill_one() - Fill a single option in message + * @m: Message to fill + * @o: Option number + * @offset: Current offset within options field, updated on insertion + */ +static void fill_one(struct msg *m, int o, int *offset) +{ + m->o[*offset] = o; + m->o[*offset + 1] = opts[o].slen; + memcpy(&m->o[*offset + 2], opts[o].s, opts[o].slen); + + opts[o].sent = 1; + *offset += 2 + opts[o].slen; +} + +/** + * fill() - Fill requested and forced options in message + * @m: Message to fill + * + * Return: current size of options field + */ +static int fill(struct msg *m) +{ + int i, o, offset = 0; + + m->op = BOOTREPLY; + m->secs = 0; + + for (o = 0; o < 255; o++) + opts[o].sent = 0; + + for (i = 0; i < opts[55].clen; i++) { + o = opts[55].c[i]; + if (opts[o].slen) + fill_one(m, o, &offset); + } + + for (o = 0; o < 255; o++) { + if (opts[o].slen && !opts[o].sent) + fill_one(m, o, &offset); + } + + m->o[offset++] = 255; + m->o[offset++] = 0; + + if (offset < 62 /* RFC 951 */) { + memset(&m->o[offset], 0, 62 - offset); + offset = 62; + } + + return offset; +} + +/** + * dhcp() - Check if this is a DHCP message, reply as needed + * @c: Execution context + * @len: Total L2 packet length + * @eh: Packet buffer, Ethernet header + * + * Return: 0 if it's not a DHCP message, 1 if handled, -1 on failure + */ +int dhcp(struct ctx *c, unsigned len, struct ethhdr *eh) +{ + struct iphdr *iph = (struct iphdr *)(eh + 1); + struct udphdr *uh = (struct udphdr *)((char *)iph + iph->ihl * 4); + struct msg *m = (struct msg *)(uh + 1); + unsigned int i, mlen = len - sizeof(*eh) - sizeof(*iph); + + if (uh->dest != htons(67)) + return 0; + + if (mlen != ntohs(uh->len) || mlen < offsetof(struct msg, o) || + m->op != BOOTREQUEST) + return -1; + + for (i = 0; i < mlen - offsetof(struct msg, o); i += m->o[i + 1] + 2) + memcpy(&opts[m->o[i]].c, &m->o[i + 2], m->o[i + 1]); + + if (opts[53].c[0] == DHCPDISCOVER) { + fprintf(stderr, "DHCP: offer to discover"); + opts[53].s[0] = DHCPOFFER; + } else if (opts[53].c[0] == DHCPREQUEST) { + fprintf(stderr, "DHCP: ack to request"); + opts[53].s[0] = DHCPACK; + } else { + return -1; + } + + fprintf(stderr, " from %02x:%02x:%02x:%02x:%02x:%02x\n\n", + m->chaddr[0], m->chaddr[1], m->chaddr[2], + m->chaddr[3], m->chaddr[4], m->chaddr[5]); + + m->yiaddr = c->addr4; + *(unsigned long *)opts[1].s = c->mask4; + *(unsigned long *)opts[3].s = c->gw4; + *(unsigned long *)opts[6].s = c->dns4; + *(unsigned long *)opts[54].s = c->gw4; + + uh->len = htons(len = offsetof(struct msg, o) + fill(m)); + uh->check = 0; + uh->source = htons(67); + uh->dest = htons(68); + + iph->tot_len = htons(len += sizeof(*iph)); + iph->daddr = c->addr4; + iph->saddr = c->gw4; + iph->check = 0; + iph->check = csum_ip4(iph, iph->ihl * 4); + + len += sizeof(*eh); + memcpy(eh->h_dest, eh->h_source, ETH_ALEN); + memcpy(eh->h_source, c->mac, ETH_ALEN); + + if (send(c->fd_unix, eh, len, 0) < 0) + perror("DHCP: send"); + + return 1; +} diff --git a/dhcp.h b/dhcp.h new file mode 100644 index 0000000..a519ee5 --- /dev/null +++ b/dhcp.h @@ -0,0 +1 @@ +int dhcp(struct ctx *c, unsigned len, struct ethhdr *eh); diff --git a/merd.c b/merd.c index b046e7e..02cb54e 100644 --- a/merd.c +++ b/merd.c @@ -28,10 +28,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -45,47 +47,15 @@ #include #include #include +#include +#include #include "merd.h" +#include "arp.h" +#include "dhcp.h" +#include "util.h" #define EPOLL_EVENTS 10 -#define CT_SIZE 4096 - -/** - * struct ct4 - IPv4 connection tracking entry - * @p: IANA protocol number - * @sa: Source address (as seen from tap interface) - * @da: Destination address - * @sp: Source port, network order - * @dp: Destination port, network order - * @hd: Destination MAC address - * @hs: Source MAC address - * @fd: File descriptor for corresponding AF_INET socket - */ -struct ct4 { - uint8_t p; - uint32_t sa; - uint32_t da; - uint16_t sp; - uint16_t dp; - unsigned char hd[ETH_ALEN]; - unsigned char hs[ETH_ALEN]; - int fd; -}; - -/** - * struct ctx - Execution context - * @epollfd: file descriptor for epoll instance - * @ext_addr4: IPv4 address for external, routable interface - * @fd_unix: AF_UNIX socket for tap file descriptor - * @map4: Connection tracking table - */ -struct ctx { - int epollfd; - unsigned long ext_addr4; - int fd_unix; - struct ct4 map4[CT_SIZE]; -}; /** * sock_unix() - Create and bind AF_UNIX socket, add to epoll list @@ -94,13 +64,12 @@ struct ctx { */ static int sock_unix(void) { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr = { .sun_family = AF_UNIX, .sun_path = UNIX_SOCK_PATH, }; - int fd; - fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("UNIX socket"); exit(EXIT_FAILURE); @@ -115,23 +84,118 @@ static int sock_unix(void) } /** - * getaddrs_ext() - Fetch IP addresses of external routable interface - * @c: Execution context - * @ifn: Name of external interface + * struct nl_request - Netlink request filled and sent by get_routes() + * @nlh: Netlink message header + * @rtm: Routing Netlink message */ -static void getaddrs_ext(struct ctx *c, const char *ifn) +struct nl_request { + struct nlmsghdr nlh; + struct rtmsg rtm; +}; + +/** + * get_routes() - Get default route and fill in routable interface name + * @c: Execution context + */ +static void get_routes(struct ctx *c) { + struct nl_request req = { + .nlh.nlmsg_type = RTM_GETROUTE, + .nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_EXCL, + .nlh.nlmsg_len = sizeof(struct nl_request), + .nlh.nlmsg_seq = 1, + .rtm.rtm_family = AF_INET, + .rtm.rtm_table = RT_TABLE_MAIN, + .rtm.rtm_scope = RT_SCOPE_UNIVERSE, + .rtm.rtm_type = RTN_UNICAST, + }; + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + int s, n, na, found = 0; + struct nlmsghdr *nlh; + struct rtattr *rta; + struct rtmsg *rtm; + char buf[BUFSIZ]; + + s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (s < 0) { + perror("netlink socket"); + goto out; + } + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("netlink bind"); + goto out; + } + + if (send(s, &req, sizeof(req), 0) < 0) { + perror("netlink send"); + goto out; + } + + n = recv(s, &buf, sizeof(buf), 0); + if (n < 0) { + perror("netlink recv"); + goto out; + } + + nlh = (struct nlmsghdr *)buf; + if (nlh->nlmsg_type == NLMSG_DONE) + goto out; + + for ( ; NLMSG_OK(nlh, n) && found < 2; NLMSG_NEXT(nlh, n)) { + rtm = (struct rtmsg *)NLMSG_DATA(nlh); + + if (rtm->rtm_dst_len) + continue; + + rta = (struct rtattr *)RTM_RTA(rtm); + na = RTM_PAYLOAD(nlh); + for ( ; RTA_OK(rta, na) && found < 2; rta = RTA_NEXT(rta, na)) { + if (rta->rta_type == RTA_GATEWAY) { + memcpy(&c->gw4, RTA_DATA(rta), sizeof(c->gw4)); + found++; + } + + if (rta->rta_type == RTA_OIF) { + if_indextoname(*(unsigned *)RTA_DATA(rta), + c->ifn); + found++; + } + } + } + +out: + close(s); + + if (found < 2) { + fprintf(stderr, "No routing information\n"); + exit(EXIT_FAILURE); + } +} + +/** + * get_addrs() - Fetch MAC, IP addresses, masks of external routable interface + * @c: Execution context + */ +static void get_addrs(struct ctx *c) +{ + struct ifreq ifr = { + .ifr_addr.sa_family = AF_INET, + }; struct ifaddrs *ifaddr, *ifa; + int s; if (getifaddrs(&ifaddr) == -1) { perror("getifaddrs"); - exit(EXIT_FAILURE); + goto out; } - for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { + for (ifa = ifaddr; ifa && !c->addr4; ifa = ifa->ifa_next) { struct sockaddr_in *in_addr; - if (strcmp(ifa->ifa_name, ifn)) + if (strcmp(ifa->ifa_name, c->ifn)) continue; if (!ifa->ifa_addr) @@ -141,13 +205,61 @@ static void getaddrs_ext(struct ctx *c, const char *ifn) continue; in_addr = (struct sockaddr_in *)ifa->ifa_addr; - c->ext_addr4 = in_addr->sin_addr.s_addr; - freeifaddrs(ifaddr); - return; + c->addr4 = in_addr->sin_addr.s_addr; + in_addr = (struct sockaddr_in *)ifa->ifa_netmask; + c->mask4 = in_addr->sin_addr.s_addr; } - fprintf(stderr, "Couldn't get IPv4 address for external interface\n"); freeifaddrs(ifaddr); + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket SIOCGIFHWADDR"); + goto out; + } + + strncpy(ifr.ifr_name, c->ifn, IF_NAMESIZE); + if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) { + perror("SIOCGIFHWADDR"); + goto out; + } + + close(s); + memcpy(c->mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + return; +out: + fprintf(stderr, "Couldn't get addresses for routable interface\n"); + exit(EXIT_FAILURE); +} + +/** + * get_dns() - Get nameserver addresses from local /etc/resolv.conf + * @c: Execution context + */ +static void get_dns(struct ctx *c) +{ + char buf[BUFSIZ], *p, *nl; + int dns4 = 0; + FILE *r; + + r = fopen("/etc/resolv.conf", "r"); + while (fgets(buf, BUFSIZ, r) && !dns4) { + if (!strstr(buf, "nameserver ")) + continue; + p = strrchr(buf, ' '); + nl = strchr(buf, '\n'); + if (nl) + *nl = 0; + if (p && inet_pton(AF_INET, p + 1, &c->dns4)) + dns4 = 1; + } + + fclose(r); + if (dns4) + return; + + fprintf(stderr, "Couldn't get IPv4 nameserver address\n"); exit(EXIT_FAILURE); } @@ -164,7 +276,7 @@ static int sock4_l4(struct ctx *c, uint16_t proto, uint16_t port) struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = port, - .sin_addr = { .s_addr = c->ext_addr4 }, + .sin_addr = { .s_addr = c->addr4 }, }; struct epoll_event ev = { 0 }; int fd; @@ -176,7 +288,7 @@ static int sock4_l4(struct ctx *c, uint16_t proto, uint16_t port) } if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { - perror("bind"); + perror("L4 bind"); close(fd); return -1; } @@ -184,69 +296,58 @@ static int sock4_l4(struct ctx *c, uint16_t proto, uint16_t port) ev.events = EPOLLIN; ev.data.fd = fd; if (epoll_ctl(c->epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) { - perror("epoll_ctl"); + perror("L4 epoll_ctl"); return -1; } return fd; } -/** - * usage() - Print usage and exit - * @name: Executable name - */ -void usage(const char *name) -{ - fprintf(stderr, "Usage: %s IF_EXT\n", name); - - exit(EXIT_FAILURE); -} - /** * lookup4() - Look up socket entry from tap-sourced packet, create if missing * @c: Execution context - * @in: Packet buffer, L2 headers + * @eh: Packet buffer, Ethernet header * * Return: -1 for unsupported or too many sockets, matching socket otherwise */ -static int lookup4(struct ctx *c, const char *in) +static int lookup4(struct ctx *c, const struct ethhdr *eh) { + struct iphdr *iph = (struct iphdr *)(eh + 1); + struct tcphdr *th = (struct tcphdr *)((char *)iph + iph->ihl * 4); char buf_s[BUFSIZ], buf_d[BUFSIZ]; struct ct4 *ct = c->map4; - struct tcphdr *th; - struct iphdr *iph; - struct ethhdr *eh; - int i; + int i, one_icmp_fd = 0; - eh = (struct ethhdr *)in; - iph = (struct iphdr *)(in + ETH_HLEN); - th = (struct tcphdr *)(iph + 1); - - switch (iph->protocol) { - case IPPROTO_ICMP: - case IPPROTO_TCP: - case IPPROTO_UDP: - break; - default: + if (iph->protocol != IPPROTO_ICMP && iph->protocol != IPPROTO_TCP && + iph->protocol != IPPROTO_UDP) return -1; - } for (i = 0; i < CT_SIZE; i++) { - if (ct[i].p == iph->protocol && - ct[i].sa == iph->saddr && ct[i].da == iph->daddr && - (ct[i].p == IPPROTO_ICMP || - (ct[i].sp == th->source && ct[i].dp == th->dest)) && + if (ct[i].p == iph->protocol && ct[i].sa == iph->saddr && + ((ct[i].p == IPPROTO_ICMP && ct[i].da == iph->daddr) + || ct[i].sp == th->source) && !memcmp(ct[i].hd, eh->h_dest, ETH_ALEN) && - !memcmp(ct[i].hs, eh->h_source, ETH_ALEN)) + !memcmp(ct[i].hs, eh->h_source, ETH_ALEN)) { + if (iph->protocol != IPPROTO_ICMP) { + ct[i].da = iph->daddr; + ct[i].dp = th->dest; + } return ct[i].fd; + } } - for (i = 0; i < CT_SIZE && ct[i].p; i++); + for (i = 0; i < CT_SIZE && ct[i].p; i++) { + if (iph->protocol == IPPROTO_ICMP) + one_icmp_fd = ct[i].fd; + } if (i == CT_SIZE) { fprintf(stderr, "\nToo many sockets, aborting "); } else { - ct[i].fd = sock4_l4(c, iph->protocol, th->source); + if (iph->protocol == IPPROTO_ICMP && one_icmp_fd) + ct[i].fd = one_icmp_fd; + else + ct[i].fd = sock4_l4(c, iph->protocol, th->source); fprintf(stderr, "\n(socket %i) New ", ct[i].fd); ct[i].p = iph->protocol; @@ -279,21 +380,19 @@ static int lookup4(struct ctx *c, const char *in) /** * lookup4_r4() - Reverse look up connection tracking entry from incoming packet * @ct: Connection tracking table - * @in: Packet buffer, L3 headers + * @fd: File descriptor that received the packet + * @iph: Packet buffer, IP header * * Return: matching entry if any, NULL otherwise */ -struct ct4 *lookup_r4(struct ct4 *ct, const char *in) +struct ct4 *lookup_r4(struct ct4 *ct, int fd, struct iphdr *iph) { - struct tcphdr *th; - struct iphdr *iph; + struct tcphdr *th = (struct tcphdr *)((char *)iph + iph->ihl * 4); int i; - iph = (struct iphdr *)in; - th = (struct tcphdr *)(iph + 1); - for (i = 0; i < CT_SIZE; i++) { - if (iph->protocol == ct[i].p && + if (ct[i].fd == fd && + iph->protocol == ct[i].p && iph->saddr == ct[i].da && (iph->protocol == IPPROTO_ICMP || (th->source == ct[i].dp && th->dest == ct[i].sp))) @@ -306,95 +405,49 @@ struct ct4 *lookup_r4(struct ct4 *ct, const char *in) /** * nat4_out() - Perform outgoing IPv4 address translation * @addr: Source address to be used - * @in: Packet buffer, L3 headers + * @iph: IP header */ -static void nat4_out(unsigned long addr, const char *in) +static void nat4_out(unsigned long addr, struct iphdr *iph) { - struct iphdr *iph = (struct iphdr *)in; - iph->saddr = addr; } /** * nat4_in() - Perform incoming IPv4 address translation * @addr: Original destination address to be used - * @in: Packet buffer, L3 headers + * @iph: IP header */ -static void nat_in(unsigned long addr, const char *in) +static void nat_in(unsigned long addr, struct iphdr *iph) { - struct iphdr *iph = (struct iphdr *)in; - iph->daddr = addr; } -/** - * csum_fold() - Fold long sum for IP and TCP checksum - * @sum: Original long sum - * - * Return: 16-bit folded sum - */ -static uint16_t csum_fold(uint32_t sum) -{ - while (sum >> 16) - sum = (sum & 0xffff) + (sum >> 16); - - return sum; -} - -/** - * csum_ipv4() - Calculate IPv4 checksum - * @buf: Packet buffer, L3 headers - * @len: Total L3 packet length - * - * Return: 16-bit IPv4-style checksum - */ -static uint16_t csum_ip4(void *buf, size_t len) -{ - uint32_t sum = 0; - uint16_t *p = buf; - size_t len1 = len / 2; - size_t off; - - for (off = 0; off < len1; off++, p++) - sum += *p; - - if (len % 2) - sum += *p & 0xff; - - return ~csum_fold(sum); -} - /** * csum_ipv4() - Calculate TCP checksum for IPv4 and set in place - * @in: Packet buffer, L3 headers + * @iph: Packet buffer, IP header */ -static void csum_tcp4(uint16_t *in) +static void csum_tcp4(struct iphdr *iph) { - struct iphdr *iph = (struct iphdr *)in; - struct tcphdr *th; - uint16_t tcp_len; + struct tcphdr *th = (struct tcphdr *)((char *)iph + iph->ihl * 4); + uint16_t tlen = ntohs(iph->tot_len) - iph->ihl * 4, *p = (uint16_t *)th; uint32_t sum = 0; - tcp_len = ntohs(iph->tot_len) - (iph->ihl << 2); - th = (struct tcphdr *)(iph + 1); - in = (uint16_t *)th; - sum += (iph->saddr >> 16) & 0xffff; sum += iph->saddr & 0xffff; sum += (iph->daddr >> 16) & 0xffff; sum += iph->daddr & 0xffff; sum += htons(IPPROTO_TCP); - sum += htons(tcp_len); + sum += htons(tlen); th->check = 0; - while (tcp_len > 1) { - sum += *in++; - tcp_len -= 2; + while (tlen > 1) { + sum += *p++; + tlen -= 2; } - if (tcp_len > 0) { - sum += *in & htons(0xff00); + if (tlen > 0) { + sum += *p & htons(0xff00); } th->check = (uint16_t)~csum_fold(sum); @@ -408,9 +461,10 @@ static void csum_tcp4(uint16_t *in) */ static void tap4_handler(struct ctx *c, int len, char *in) { - struct iphdr *iph = (struct iphdr *)(in + ETH_HLEN); - struct tcphdr *th = (struct tcphdr *)(iph + 1); - struct udphdr *uh = (struct udphdr *)(iph + 1); + struct ethhdr *eh = (struct ethhdr *)in; + struct iphdr *iph = (struct iphdr *)(eh + 1); + struct tcphdr *th = (struct tcphdr *)((char *)iph + iph->ihl * 4); + struct udphdr *uh = (struct udphdr *)th; struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = th->dest, @@ -419,7 +473,10 @@ static void tap4_handler(struct ctx *c, int len, char *in) char buf_s[BUFSIZ], buf_d[BUFSIZ]; int fd; - fd = lookup4(c, in); + if (arp(c, len, eh) || dhcp(c, len, eh)) + return; + + fd = lookup4(c, eh); if (fd == -1) return; @@ -438,65 +495,56 @@ static void tap4_handler(struct ctx *c, int len, char *in) fd); } - nat4_out(c->ext_addr4, in + ETH_HLEN); - - switch (iph->protocol) { - case IPPROTO_TCP: - csum_tcp4((uint16_t *)(in + ETH_HLEN)); - break; - case IPPROTO_UDP: + if (iph->protocol == IPPROTO_TCP) + csum_tcp4(iph); + else if (iph->protocol == IPPROTO_UDP) uh->check = 0; - break; - case IPPROTO_ICMP: - break; - default: + else if (iph->protocol != IPPROTO_ICMP) return; - } - if (sendto(fd, in + sizeof(struct ethhdr) + sizeof(struct iphdr), - len - sizeof(struct ethhdr) - 4 * iph->ihl, 0, + nat4_out(c->addr4, iph); + + if (sendto(fd, (void *)th, len - sizeof(*eh) - iph->ihl * 4, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) perror("sendto"); } /** - * tap4_handler() - Packet handler for external routable interface + * ext4_handler() - Packet handler for external routable interface * @c: Execution context + * @fd: File descriptor that received the packet * @len: Total L3 packet length * @in: Packet buffer, L3 headers */ -static void ext4_handler(struct ctx *c, int len, char *in) +static void ext4_handler(struct ctx *c, int fd, int len, char *in) { struct iphdr *iph = (struct iphdr *)in; - struct tcphdr *th = (struct tcphdr *)(iph + 1); - char buf_s[BUFSIZ], buf_d[BUFSIZ]; - struct ethhdr *eh; + struct tcphdr *th = (struct tcphdr *)((char *)iph + iph->ihl * 4); + struct udphdr *uh = (struct udphdr *)th; + char buf_s[BUFSIZ], buf_d[BUFSIZ], buf[ETH_MAX_MTU]; + struct ethhdr *eh = (struct ethhdr *)buf; struct ct4 *entry; - char buf[1 << 16]; - entry = lookup_r4(c->map4, in); + entry = lookup_r4(c->map4, fd, iph); if (!entry) return; - nat_in(entry->sa, in); + nat_in(entry->sa, iph); iph->check = 0; - iph->check = csum_ip4(iph, 4 * iph->ihl); + iph->check = csum_ip4(iph, iph->ihl * 4); if (iph->protocol == IPPROTO_TCP) - csum_tcp4((uint16_t *)in); - else if (iph->protocol == IPPROTO_UDP) { - struct udphdr *uh = (struct udphdr *)(iph + 1); + csum_tcp4(iph); + else if (iph->protocol == IPPROTO_UDP) uh->check = 0; - } - eh = (struct ethhdr *)buf; memcpy(eh->h_dest, entry->hs, ETH_ALEN); memcpy(eh->h_source, entry->hd, ETH_ALEN); eh->h_proto = ntohs(ETH_P_IP); - memcpy(buf + sizeof(struct ethhdr), in, len); + memcpy(eh + 1, in, len); if (iph->protocol == IPPROTO_ICMP) { fprintf(stderr, "icmp (socket %i) to tap: %s -> %s\n", @@ -513,10 +561,21 @@ static void ext4_handler(struct ctx *c, int len, char *in) ntohs(th->dest)); } - if (send(c->fd_unix, buf, len + sizeof(struct ethhdr), 0) < 0) + if (send(c->fd_unix, buf, len + sizeof(*eh), 0) < 0) perror("send"); } +/** + * usage() - Print usage and exit + * @name: Executable name + */ +void usage(const char *name) +{ + fprintf(stderr, "Usage: %s\n", name); + + exit(EXIT_FAILURE); +} + /** * main() - Entry point and main loop * @argc: Argument count @@ -527,18 +586,30 @@ static void ext4_handler(struct ctx *c, int len, char *in) int main(int argc, char **argv) { struct epoll_event events[EPOLL_EVENTS]; + char buf4[4][sizeof("255.255.255.255")]; struct epoll_event ev = { 0 }; + char buf[ETH_MAX_MTU]; struct ctx c = { 0 }; - const char *if_ext; - char buf[1 << 16]; int nfds, i, len; int fd_unix; - if (argc != 2) + if (argc != 1) usage(argv[0]); - if_ext = argv[1]; - getaddrs_ext(&c, if_ext); + get_routes(&c); + get_addrs(&c); + get_dns(&c); + + fprintf(stderr, "ARP:\n"); + fprintf(stderr, "\taddress: %02x:%02x:%02x:%02x:%02x:%02x from %s\n", + c.mac[0], c.mac[1], c.mac[2], c.mac[3], c.mac[4], c.mac[5], + c.ifn); + fprintf(stderr, "DHCP:\n"); + fprintf(stderr, "\tassign: %s, mask: %s, router: %s, DNS: %s\n\n", + inet_ntop(AF_INET, &c.addr4, buf4[0], sizeof(buf4[0])), + inet_ntop(AF_INET, &c.mask4, buf4[1], sizeof(buf4[1])), + inet_ntop(AF_INET, &c.gw4, buf4[2], sizeof(buf4[2])), + inet_ntop(AF_INET, &c.dns4, buf4[3], sizeof(buf4[3]))); c.epollfd = epoll_create1(0); if (c.epollfd == -1) { @@ -549,18 +620,17 @@ int main(int argc, char **argv) fd_unix = sock_unix(); listen: listen(fd_unix, 1); - fprintf(stderr, - "You can now start qrap:\n\t" - "./qrap 42 kvm ... -net tap,fd=42 -net nic,model=virtio ...\n"); - c.fd_unix = accept(fd_unix, NULL, NULL); ev.events = EPOLLIN; ev.data.fd = c.fd_unix; epoll_ctl(c.epollfd, EPOLL_CTL_ADD, c.fd_unix, &ev); + fprintf(stderr, + "You can now start qrap:\n\t" + "./qrap 42 kvm ... -net tap,fd=42 -net nic,model=virtio\n\n"); loop: nfds = epoll_wait(c.epollfd, events, EPOLL_EVENTS, -1); - if (nfds == -1) { + if (nfds == -1 && errno != EINTR) { perror("epoll_wait"); exit(EXIT_FAILURE); } @@ -574,7 +644,7 @@ loop: goto listen; } - if (len == 0) + if (len == 0 || (len < 0 && errno == EINTR)) continue; if (len < 0) { @@ -586,7 +656,7 @@ loop: if (events[i].data.fd == c.fd_unix) tap4_handler(&c, len, buf); else - ext4_handler(&c, len, buf); + ext4_handler(&c, events[i].data.fd, len, buf); } goto loop; diff --git a/merd.h b/merd.h new file mode 100644 index 0000000..78e3c0b --- /dev/null +++ b/merd.h @@ -0,0 +1,47 @@ +#define CT_SIZE 4096 +#define UNIX_SOCK_PATH "/tmp/merd.socket" + +/** + * struct ct4 - IPv4 connection tracking entry + * @p: IANA protocol number + * @sa: Source address (as seen from tap interface) + * @da: Destination address + * @sp: Source port, network order + * @dp: Destination port, network order + * @hd: Destination MAC address + * @hs: Source MAC address + * @fd: File descriptor for corresponding AF_INET socket + */ +struct ct4 { + uint8_t p; + uint32_t sa; + uint32_t da; + uint16_t sp; + uint16_t dp; + unsigned char hd[ETH_ALEN]; + unsigned char hs[ETH_ALEN]; + int fd; +}; + +/** + * struct ctx - Execution context + * @epollfd: file descriptor for epoll instance + * @fd_unix: AF_UNIX socket for tap file descriptor + * @map4: Connection tracking table + * @addr4: IPv4 address for external, routable interface + * @mask4: IPv4 netmask, network order + * @gw4: Default IPv4 gateway, network order + * @dns4: IPv4 DNS address, network order + * @ifn: Name of routable interface + */ +struct ctx { + int epollfd; + int fd_unix; + struct ct4 map4[CT_SIZE]; + unsigned char mac[ETH_ALEN]; + unsigned long addr4; + unsigned long mask4; + unsigned long gw4; + unsigned long dns4; + char ifn[IF_NAMESIZE]; +}; diff --git a/util.c b/util.c new file mode 100644 index 0000000..23eccb1 --- /dev/null +++ b/util.c @@ -0,0 +1,48 @@ +/* MERD - MacVTap Egress and Routing Daemon + * + * util.c - Convenience helpers + * + * Author: Stefano Brivio + * License: GPLv2 + * + */ + +#include +#include + +/** + * csum_fold() - Fold long sum for IP and TCP checksum + * @sum: Original long sum + * + * Return: 16-bit folded sum + */ +uint16_t csum_fold(uint32_t sum) +{ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return sum; +} + +/** + * csum_ipv4() - Calculate IPv4 checksum + * @buf: Packet buffer, L3 headers + * @len: Total L3 packet length + * + * Return: 16-bit IPv4-style checksum + */ +uint16_t csum_ip4(void *buf, size_t len) +{ + uint32_t sum = 0; + uint16_t *p = buf; + size_t len1 = len / 2; + size_t off; + + for (off = 0; off < len1; off++, p++) + sum += *p; + + if (len % 2) + sum += *p & 0xff; + + return ~csum_fold(sum); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..282820c --- /dev/null +++ b/util.h @@ -0,0 +1,2 @@ +uint16_t csum_fold(uint32_t sum); +uint16_t csum_ip4(void *buf, size_t len);