merd: ARP and DHCP handlers, connection tracking fixes
With this, merd provides a fully functional IPv4 environment to guests, requiring a single capability, CAP_NET_RAW. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
This commit is contained in:
parent
fa2d20908d
commit
b439984641
9 changed files with 673 additions and 204 deletions
4
Makefile
4
Makefile
|
@ -2,8 +2,8 @@ CFLAGS += -Wall -Wextra -pedantic
|
||||||
|
|
||||||
all: merd qrap
|
all: merd qrap
|
||||||
|
|
||||||
merd: merd.c merd.h
|
merd: merd.c merd.h arp.c arp.h dhcp.c dhcp.h util.c util.h
|
||||||
$(CC) $(CFLAGS) merd.c -o merd
|
$(CC) $(CFLAGS) merd.c arp.c dhcp.c util.c -o merd
|
||||||
|
|
||||||
qrap: qrap.c merd.h
|
qrap: qrap.c merd.h
|
||||||
$(CC) $(CFLAGS) qrap.o -o qrap
|
$(CC) $(CFLAGS) qrap.o -o qrap
|
||||||
|
|
82
arp.c
Normal file
82
arp.c
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/* MERD - MacVTap Egress and Routing Daemon
|
||||||
|
*
|
||||||
|
* arp.c - ARP implementation
|
||||||
|
*
|
||||||
|
* Author: Stefano Brivio <sbrivio@redhat.com>
|
||||||
|
* License: GPLv2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <net/if_arp.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
1
arp.h
Normal file
1
arp.h
Normal file
|
@ -0,0 +1 @@
|
||||||
|
int arp(struct ctx *c, unsigned len, struct ethhdr *eh);
|
218
dhcp.c
Normal file
218
dhcp.c
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
/* MERD - MacVTap Egress and Routing Daemon
|
||||||
|
*
|
||||||
|
* dhcp.c - Minimalistic DHCP server for MERD
|
||||||
|
*
|
||||||
|
* Author: Stefano Brivio <sbrivio@redhat.com>
|
||||||
|
* License: GPLv2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/udp.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
1
dhcp.h
Normal file
1
dhcp.h
Normal file
|
@ -0,0 +1 @@
|
||||||
|
int dhcp(struct ctx *c, unsigned len, struct ethhdr *eh);
|
472
merd.c
472
merd.c
|
@ -28,10 +28,12 @@
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <ifaddrs.h>
|
#include <ifaddrs.h>
|
||||||
#include <linux/if_ether.h>
|
#include <linux/if_ether.h>
|
||||||
#include <linux/if_packet.h>
|
#include <linux/if_packet.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
#include <linux/ipv6.h>
|
#include <linux/ipv6.h>
|
||||||
#include <linux/tcp.h>
|
#include <linux/tcp.h>
|
||||||
#include <linux/udp.h>
|
#include <linux/udp.h>
|
||||||
|
@ -45,47 +47,15 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <linux/ip.h>
|
#include <linux/ip.h>
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
|
||||||
#include "merd.h"
|
#include "merd.h"
|
||||||
|
#include "arp.h"
|
||||||
|
#include "dhcp.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
#define EPOLL_EVENTS 10
|
#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
|
* sock_unix() - Create and bind AF_UNIX socket, add to epoll list
|
||||||
|
@ -94,13 +64,12 @@ struct ctx {
|
||||||
*/
|
*/
|
||||||
static int sock_unix(void)
|
static int sock_unix(void)
|
||||||
{
|
{
|
||||||
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
struct sockaddr_un addr = {
|
struct sockaddr_un addr = {
|
||||||
.sun_family = AF_UNIX,
|
.sun_family = AF_UNIX,
|
||||||
.sun_path = UNIX_SOCK_PATH,
|
.sun_path = UNIX_SOCK_PATH,
|
||||||
};
|
};
|
||||||
int fd;
|
|
||||||
|
|
||||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
perror("UNIX socket");
|
perror("UNIX socket");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
|
@ -115,23 +84,118 @@ static int sock_unix(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getaddrs_ext() - Fetch IP addresses of external routable interface
|
* struct nl_request - Netlink request filled and sent by get_routes()
|
||||||
* @c: Execution context
|
* @nlh: Netlink message header
|
||||||
* @ifn: Name of external interface
|
* @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;
|
struct ifaddrs *ifaddr, *ifa;
|
||||||
|
int s;
|
||||||
|
|
||||||
if (getifaddrs(&ifaddr) == -1) {
|
if (getifaddrs(&ifaddr) == -1) {
|
||||||
perror("getifaddrs");
|
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;
|
struct sockaddr_in *in_addr;
|
||||||
|
|
||||||
if (strcmp(ifa->ifa_name, ifn))
|
if (strcmp(ifa->ifa_name, c->ifn))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!ifa->ifa_addr)
|
if (!ifa->ifa_addr)
|
||||||
|
@ -141,13 +205,61 @@ static void getaddrs_ext(struct ctx *c, const char *ifn)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
in_addr = (struct sockaddr_in *)ifa->ifa_addr;
|
in_addr = (struct sockaddr_in *)ifa->ifa_addr;
|
||||||
c->ext_addr4 = in_addr->sin_addr.s_addr;
|
c->addr4 = in_addr->sin_addr.s_addr;
|
||||||
freeifaddrs(ifaddr);
|
in_addr = (struct sockaddr_in *)ifa->ifa_netmask;
|
||||||
return;
|
c->mask4 = in_addr->sin_addr.s_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "Couldn't get IPv4 address for external interface\n");
|
|
||||||
freeifaddrs(ifaddr);
|
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);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +276,7 @@ static int sock4_l4(struct ctx *c, uint16_t proto, uint16_t port)
|
||||||
struct sockaddr_in addr = {
|
struct sockaddr_in addr = {
|
||||||
.sin_family = AF_INET,
|
.sin_family = AF_INET,
|
||||||
.sin_port = port,
|
.sin_port = port,
|
||||||
.sin_addr = { .s_addr = c->ext_addr4 },
|
.sin_addr = { .s_addr = c->addr4 },
|
||||||
};
|
};
|
||||||
struct epoll_event ev = { 0 };
|
struct epoll_event ev = { 0 };
|
||||||
int fd;
|
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) {
|
if (bind(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||||
perror("bind");
|
perror("L4 bind");
|
||||||
close(fd);
|
close(fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -184,68 +296,57 @@ static int sock4_l4(struct ctx *c, uint16_t proto, uint16_t port)
|
||||||
ev.events = EPOLLIN;
|
ev.events = EPOLLIN;
|
||||||
ev.data.fd = fd;
|
ev.data.fd = fd;
|
||||||
if (epoll_ctl(c->epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
|
if (epoll_ctl(c->epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
|
||||||
perror("epoll_ctl");
|
perror("L4 epoll_ctl");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fd;
|
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
|
* lookup4() - Look up socket entry from tap-sourced packet, create if missing
|
||||||
* @c: Execution context
|
* @c: Execution context
|
||||||
* @in: Packet buffer, L2 headers
|
* @eh: Packet buffer, Ethernet header
|
||||||
*
|
*
|
||||||
* Return: -1 for unsupported or too many sockets, matching socket otherwise
|
* 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];
|
char buf_s[BUFSIZ], buf_d[BUFSIZ];
|
||||||
struct ct4 *ct = c->map4;
|
struct ct4 *ct = c->map4;
|
||||||
struct tcphdr *th;
|
int i, one_icmp_fd = 0;
|
||||||
struct iphdr *iph;
|
|
||||||
struct ethhdr *eh;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
eh = (struct ethhdr *)in;
|
if (iph->protocol != IPPROTO_ICMP && iph->protocol != IPPROTO_TCP &&
|
||||||
iph = (struct iphdr *)(in + ETH_HLEN);
|
iph->protocol != IPPROTO_UDP)
|
||||||
th = (struct tcphdr *)(iph + 1);
|
|
||||||
|
|
||||||
switch (iph->protocol) {
|
|
||||||
case IPPROTO_ICMP:
|
|
||||||
case IPPROTO_TCP:
|
|
||||||
case IPPROTO_UDP:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < CT_SIZE; i++) {
|
for (i = 0; i < CT_SIZE; i++) {
|
||||||
if (ct[i].p == iph->protocol &&
|
if (ct[i].p == iph->protocol && ct[i].sa == iph->saddr &&
|
||||||
ct[i].sa == iph->saddr && ct[i].da == iph->daddr &&
|
((ct[i].p == IPPROTO_ICMP && ct[i].da == iph->daddr)
|
||||||
(ct[i].p == IPPROTO_ICMP ||
|
|| ct[i].sp == th->source) &&
|
||||||
(ct[i].sp == th->source && ct[i].dp == th->dest)) &&
|
|
||||||
!memcmp(ct[i].hd, eh->h_dest, ETH_ALEN) &&
|
!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;
|
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) {
|
if (i == CT_SIZE) {
|
||||||
fprintf(stderr, "\nToo many sockets, aborting ");
|
fprintf(stderr, "\nToo many sockets, aborting ");
|
||||||
} else {
|
} else {
|
||||||
|
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);
|
ct[i].fd = sock4_l4(c, iph->protocol, th->source);
|
||||||
|
|
||||||
fprintf(stderr, "\n(socket %i) New ", ct[i].fd);
|
fprintf(stderr, "\n(socket %i) New ", ct[i].fd);
|
||||||
|
@ -279,21 +380,19 @@ static int lookup4(struct ctx *c, const char *in)
|
||||||
/**
|
/**
|
||||||
* lookup4_r4() - Reverse look up connection tracking entry from incoming packet
|
* lookup4_r4() - Reverse look up connection tracking entry from incoming packet
|
||||||
* @ct: Connection tracking table
|
* @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
|
* 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 tcphdr *th = (struct tcphdr *)((char *)iph + iph->ihl * 4);
|
||||||
struct iphdr *iph;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
iph = (struct iphdr *)in;
|
|
||||||
th = (struct tcphdr *)(iph + 1);
|
|
||||||
|
|
||||||
for (i = 0; i < CT_SIZE; i++) {
|
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->saddr == ct[i].da &&
|
||||||
(iph->protocol == IPPROTO_ICMP ||
|
(iph->protocol == IPPROTO_ICMP ||
|
||||||
(th->source == ct[i].dp && th->dest == ct[i].sp)))
|
(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
|
* nat4_out() - Perform outgoing IPv4 address translation
|
||||||
* @addr: Source address to be used
|
* @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;
|
iph->saddr = addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* nat4_in() - Perform incoming IPv4 address translation
|
* nat4_in() - Perform incoming IPv4 address translation
|
||||||
* @addr: Original destination address to be used
|
* @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;
|
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
|
* 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 = (struct tcphdr *)((char *)iph + iph->ihl * 4);
|
||||||
struct tcphdr *th;
|
uint16_t tlen = ntohs(iph->tot_len) - iph->ihl * 4, *p = (uint16_t *)th;
|
||||||
uint16_t tcp_len;
|
|
||||||
uint32_t sum = 0;
|
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 >> 16) & 0xffff;
|
||||||
sum += iph->saddr & 0xffff;
|
sum += iph->saddr & 0xffff;
|
||||||
sum += (iph->daddr >> 16) & 0xffff;
|
sum += (iph->daddr >> 16) & 0xffff;
|
||||||
sum += iph->daddr & 0xffff;
|
sum += iph->daddr & 0xffff;
|
||||||
|
|
||||||
sum += htons(IPPROTO_TCP);
|
sum += htons(IPPROTO_TCP);
|
||||||
sum += htons(tcp_len);
|
sum += htons(tlen);
|
||||||
|
|
||||||
th->check = 0;
|
th->check = 0;
|
||||||
while (tcp_len > 1) {
|
while (tlen > 1) {
|
||||||
sum += *in++;
|
sum += *p++;
|
||||||
tcp_len -= 2;
|
tlen -= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tcp_len > 0) {
|
if (tlen > 0) {
|
||||||
sum += *in & htons(0xff00);
|
sum += *p & htons(0xff00);
|
||||||
}
|
}
|
||||||
|
|
||||||
th->check = (uint16_t)~csum_fold(sum);
|
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)
|
static void tap4_handler(struct ctx *c, int len, char *in)
|
||||||
{
|
{
|
||||||
struct iphdr *iph = (struct iphdr *)(in + ETH_HLEN);
|
struct ethhdr *eh = (struct ethhdr *)in;
|
||||||
struct tcphdr *th = (struct tcphdr *)(iph + 1);
|
struct iphdr *iph = (struct iphdr *)(eh + 1);
|
||||||
struct udphdr *uh = (struct udphdr *)(iph + 1);
|
struct tcphdr *th = (struct tcphdr *)((char *)iph + iph->ihl * 4);
|
||||||
|
struct udphdr *uh = (struct udphdr *)th;
|
||||||
struct sockaddr_in addr = {
|
struct sockaddr_in addr = {
|
||||||
.sin_family = AF_INET,
|
.sin_family = AF_INET,
|
||||||
.sin_port = th->dest,
|
.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];
|
char buf_s[BUFSIZ], buf_d[BUFSIZ];
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
fd = lookup4(c, in);
|
if (arp(c, len, eh) || dhcp(c, len, eh))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fd = lookup4(c, eh);
|
||||||
if (fd == -1)
|
if (fd == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -438,65 +495,56 @@ static void tap4_handler(struct ctx *c, int len, char *in)
|
||||||
fd);
|
fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
nat4_out(c->ext_addr4, in + ETH_HLEN);
|
if (iph->protocol == IPPROTO_TCP)
|
||||||
|
csum_tcp4(iph);
|
||||||
switch (iph->protocol) {
|
else if (iph->protocol == IPPROTO_UDP)
|
||||||
case IPPROTO_TCP:
|
|
||||||
csum_tcp4((uint16_t *)(in + ETH_HLEN));
|
|
||||||
break;
|
|
||||||
case IPPROTO_UDP:
|
|
||||||
uh->check = 0;
|
uh->check = 0;
|
||||||
break;
|
else if (iph->protocol != IPPROTO_ICMP)
|
||||||
case IPPROTO_ICMP:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (sendto(fd, in + sizeof(struct ethhdr) + sizeof(struct iphdr),
|
nat4_out(c->addr4, iph);
|
||||||
len - sizeof(struct ethhdr) - 4 * iph->ihl, 0,
|
|
||||||
|
if (sendto(fd, (void *)th, len - sizeof(*eh) - iph->ihl * 4, 0,
|
||||||
(struct sockaddr *)&addr, sizeof(addr)) < 0)
|
(struct sockaddr *)&addr, sizeof(addr)) < 0)
|
||||||
perror("sendto");
|
perror("sendto");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tap4_handler() - Packet handler for external routable interface
|
* ext4_handler() - Packet handler for external routable interface
|
||||||
* @c: Execution context
|
* @c: Execution context
|
||||||
|
* @fd: File descriptor that received the packet
|
||||||
* @len: Total L3 packet length
|
* @len: Total L3 packet length
|
||||||
* @in: Packet buffer, L3 headers
|
* @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 iphdr *iph = (struct iphdr *)in;
|
||||||
struct tcphdr *th = (struct tcphdr *)(iph + 1);
|
struct tcphdr *th = (struct tcphdr *)((char *)iph + iph->ihl * 4);
|
||||||
char buf_s[BUFSIZ], buf_d[BUFSIZ];
|
struct udphdr *uh = (struct udphdr *)th;
|
||||||
struct ethhdr *eh;
|
char buf_s[BUFSIZ], buf_d[BUFSIZ], buf[ETH_MAX_MTU];
|
||||||
|
struct ethhdr *eh = (struct ethhdr *)buf;
|
||||||
struct ct4 *entry;
|
struct ct4 *entry;
|
||||||
char buf[1 << 16];
|
|
||||||
|
|
||||||
entry = lookup_r4(c->map4, in);
|
entry = lookup_r4(c->map4, fd, iph);
|
||||||
if (!entry)
|
if (!entry)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
nat_in(entry->sa, in);
|
nat_in(entry->sa, iph);
|
||||||
|
|
||||||
iph->check = 0;
|
iph->check = 0;
|
||||||
iph->check = csum_ip4(iph, 4 * iph->ihl);
|
iph->check = csum_ip4(iph, iph->ihl * 4);
|
||||||
|
|
||||||
if (iph->protocol == IPPROTO_TCP)
|
if (iph->protocol == IPPROTO_TCP)
|
||||||
csum_tcp4((uint16_t *)in);
|
csum_tcp4(iph);
|
||||||
else if (iph->protocol == IPPROTO_UDP) {
|
else if (iph->protocol == IPPROTO_UDP)
|
||||||
struct udphdr *uh = (struct udphdr *)(iph + 1);
|
|
||||||
uh->check = 0;
|
uh->check = 0;
|
||||||
}
|
|
||||||
|
|
||||||
eh = (struct ethhdr *)buf;
|
|
||||||
memcpy(eh->h_dest, entry->hs, ETH_ALEN);
|
memcpy(eh->h_dest, entry->hs, ETH_ALEN);
|
||||||
memcpy(eh->h_source, entry->hd, ETH_ALEN);
|
memcpy(eh->h_source, entry->hd, ETH_ALEN);
|
||||||
eh->h_proto = ntohs(ETH_P_IP);
|
eh->h_proto = ntohs(ETH_P_IP);
|
||||||
|
|
||||||
memcpy(buf + sizeof(struct ethhdr), in, len);
|
memcpy(eh + 1, in, len);
|
||||||
|
|
||||||
if (iph->protocol == IPPROTO_ICMP) {
|
if (iph->protocol == IPPROTO_ICMP) {
|
||||||
fprintf(stderr, "icmp (socket %i) to tap: %s -> %s\n",
|
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));
|
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");
|
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
|
* main() - Entry point and main loop
|
||||||
* @argc: Argument count
|
* @argc: Argument count
|
||||||
|
@ -527,18 +586,30 @@ static void ext4_handler(struct ctx *c, int len, char *in)
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
struct epoll_event events[EPOLL_EVENTS];
|
struct epoll_event events[EPOLL_EVENTS];
|
||||||
|
char buf4[4][sizeof("255.255.255.255")];
|
||||||
struct epoll_event ev = { 0 };
|
struct epoll_event ev = { 0 };
|
||||||
|
char buf[ETH_MAX_MTU];
|
||||||
struct ctx c = { 0 };
|
struct ctx c = { 0 };
|
||||||
const char *if_ext;
|
|
||||||
char buf[1 << 16];
|
|
||||||
int nfds, i, len;
|
int nfds, i, len;
|
||||||
int fd_unix;
|
int fd_unix;
|
||||||
|
|
||||||
if (argc != 2)
|
if (argc != 1)
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
|
|
||||||
if_ext = argv[1];
|
get_routes(&c);
|
||||||
getaddrs_ext(&c, if_ext);
|
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);
|
c.epollfd = epoll_create1(0);
|
||||||
if (c.epollfd == -1) {
|
if (c.epollfd == -1) {
|
||||||
|
@ -549,18 +620,17 @@ int main(int argc, char **argv)
|
||||||
fd_unix = sock_unix();
|
fd_unix = sock_unix();
|
||||||
listen:
|
listen:
|
||||||
listen(fd_unix, 1);
|
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);
|
c.fd_unix = accept(fd_unix, NULL, NULL);
|
||||||
ev.events = EPOLLIN;
|
ev.events = EPOLLIN;
|
||||||
ev.data.fd = c.fd_unix;
|
ev.data.fd = c.fd_unix;
|
||||||
epoll_ctl(c.epollfd, EPOLL_CTL_ADD, c.fd_unix, &ev);
|
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:
|
loop:
|
||||||
nfds = epoll_wait(c.epollfd, events, EPOLL_EVENTS, -1);
|
nfds = epoll_wait(c.epollfd, events, EPOLL_EVENTS, -1);
|
||||||
if (nfds == -1) {
|
if (nfds == -1 && errno != EINTR) {
|
||||||
perror("epoll_wait");
|
perror("epoll_wait");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
@ -574,7 +644,7 @@ loop:
|
||||||
goto listen;
|
goto listen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len == 0)
|
if (len == 0 || (len < 0 && errno == EINTR))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
|
@ -586,7 +656,7 @@ loop:
|
||||||
if (events[i].data.fd == c.fd_unix)
|
if (events[i].data.fd == c.fd_unix)
|
||||||
tap4_handler(&c, len, buf);
|
tap4_handler(&c, len, buf);
|
||||||
else
|
else
|
||||||
ext4_handler(&c, len, buf);
|
ext4_handler(&c, events[i].data.fd, len, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
goto loop;
|
goto loop;
|
||||||
|
|
47
merd.h
Normal file
47
merd.h
Normal file
|
@ -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];
|
||||||
|
};
|
48
util.c
Normal file
48
util.c
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/* MERD - MacVTap Egress and Routing Daemon
|
||||||
|
*
|
||||||
|
* util.c - Convenience helpers
|
||||||
|
*
|
||||||
|
* Author: Stefano Brivio <sbrivio@redhat.com>
|
||||||
|
* License: GPLv2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
2
util.h
Normal file
2
util.h
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
uint16_t csum_fold(uint32_t sum);
|
||||||
|
uint16_t csum_ip4(void *buf, size_t len);
|
Loading…
Reference in a new issue