From 7339b55944e97077e4f74c4be34cd956ae44198b Mon Sep 17 00:00:00 2001 From: rich cannings Date: Wed, 16 Feb 2011 13:43:44 -0800 Subject: Add user mode networking restrictions: a firewall Command line options added and code is supported for: QEMU_OPTION_drop_udp QEMU_OPTION_drop_tcp QEMU_OPTION_allow_tcp QEMU_OPTION_drop_log QEMU_OPTION_net_forward QEMU_OPTION_max_dns_conns QEMU_OPTION_allow_udp QEMU_OPTION_dns_log Also, this change makes the default max DNS connections unlimited. Change-Id: I887213149956dda155ef514418365bd80d8f1236 --- slirp-android/libslirp.h | 57 +++++++++ slirp-android/slirp.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++ slirp-android/socket.c | 21 +++- slirp-android/tcp_subr.c | 169 +++++++++++++++++++++----- slirp-android/udp.c | 51 ++++++++ slirp-android/udp.h | 4 - 6 files changed, 568 insertions(+), 34 deletions(-) (limited to 'slirp-android') diff --git a/slirp-android/libslirp.h b/slirp-android/libslirp.h index 6086384..120e3d3 100644 --- a/slirp-android/libslirp.h +++ b/slirp-android/libslirp.h @@ -2,7 +2,9 @@ #define _LIBSLIRP_H #include +#include #include "sockets.h" +#include "slirp.h" #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # define socket_close winsock2_socket_close3 @@ -16,6 +18,8 @@ extern "C" { #endif +struct mbuf; + int inet_strtoip(const char* str, uint32_t *ip); char* inet_iptostr(uint32_t ip); @@ -32,6 +36,59 @@ void slirp_input(const uint8_t *pkt, int pkt_len); int slirp_can_output(void); void slirp_output(const uint8_t *pkt, int pkt_len); +/* ---------------------------------------------------*/ +/* User mode network stack restrictions */ +void slirp_drop_udp(); +void slirp_drop_tcp(); +void slirp_add_allow(unsigned long dst_addr, int dst_lport, + int dst_hport, u_int8_t proto); +void slirp_drop_log_fd(FILE* fd); +int slirp_should_drop(unsigned long dst_addr, + int dst_port, + u_int8_t proto); +int slirp_drop_log(const char* format, ...); + +/* for network forwards */ +void slirp_add_net_forward(unsigned long dest_ip, unsigned long dest_mask, + int dest_lport, int dest_hport, + unsigned long redirect_ip, int redirect_port); + +int slirp_should_net_forward(unsigned long remote_ip, int remote_port, + unsigned long *redirect_ip, int *redirect_port); +/* ---------------------------------------------------*/ + +/** + * Additional network stack modifications, aiming to detect and log + * any network activity initiated by any binary outisde the context of + * the running browser. + */ + +void slirp_dns_log_fd(FILE* fd); +/** Logs the DNS name in DNS query issued by the VM. */ +int slirp_log_dns(struct mbuf* m, int dropped); +/** IP packet dump of DNS queris and responses. */ +int slirp_dump_dns(struct mbuf* m); +/** Sets an upper limit for the number of allowed DNS requests from + * the VM. + */ +void slirp_set_max_dns_conns(int max_dns_conns); +/* Returns the max number of allowed DNS requests.*/ +int slirp_get_max_dns_conns(); + +/** + * Modifications for implementing "-net-forward-tcp2sink' option. + */ + +void slirp_forward_dropped_tcp2sink(unsigned long sink_ip, int sink_port); +int slirp_should_forward_dropped_tcp2sink(); +unsigned long slirp_get_tcp_sink_ip(); +int slirp_get_tcp_sink_port(); + + + + +/* ---------------------------------------------------*/ + void slirp_redir_loop(void (*func)(void *opaque, int is_udp, const SockAddress *laddr, const SockAddress *faddr), diff --git a/slirp-android/slirp.c b/slirp-android/slirp.c index e3c4c42..683bef5 100644 --- a/slirp-android/slirp.c +++ b/slirp-android/slirp.c @@ -32,6 +32,12 @@ #include "android/android.h" #include "sockets.h" +#include + +/* proto types */ +static void slirp_net_forward_init(void); + + #define D(...) VERBOSE_PRINT(slirp,__VA_ARGS__) #define DN(...) do { if (VERBOSE_CHECK(slirp)) dprintn(__VA_ARGS__); } while (0) @@ -259,6 +265,8 @@ void slirp_init(int restricted, const char *special_ip) alias_addr_ip = special_addr_ip | CTL_ALIAS; getouraddr(); register_savevm("slirp", 0, 1, slirp_state_save, slirp_state_load, NULL); + + slirp_net_forward_init(); } #define CONN_CANFSEND(so) (((so)->so_state & (SS_FCANTSENDMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED) @@ -819,6 +827,298 @@ void if_encap(const uint8_t *ip_data, int ip_data_len) } } + +/*---------------------------------------------------*/ +/* User mode network stack restrictions */ +struct fw_allow_entry { + struct fw_allow_entry* next; + unsigned long dst_addr; /* host byte order */ + /* Allowed port range. dst_lport should be the same as dst_hport for a + * single port. */ + unsigned short dst_lport; /* host byte order */ + unsigned short dst_hport; /* host byte order */ +}; + +static int drop_udp = 0; +static int drop_tcp = 0; +static struct fw_allow_entry* allow_tcp_entries = NULL; +static struct fw_allow_entry* allow_udp_entries = NULL; +static FILE* drop_log_fd = NULL; +static FILE* dns_log_fd = NULL; +static int max_dns_conns = -1; /* unlimited max DNS connections by default */ +static int slirp_net_forward_inited = 0; + +void slirp_drop_udp() { + drop_udp = 1; +} + +void slirp_drop_tcp() { + drop_tcp = 1; +} + +/* TCP traffic forwarding to a sink - If enabled, all TCP traffic to any + * ip/port that is not explicitly forwared using '-net-forward', and which would + * otherwise be dropped if '-drop-tcp' has been specified, is redirected to the + * specified ip:port + */ +int forward_dropped_tcp2sink = 0; +static unsigned long tcp_sink_ip; +int tcp_sink_port; + +void slirp_forward_dropped_tcp2sink(unsigned long sink_ip, int sink_port) { + tcp_sink_ip = sink_ip; + tcp_sink_port = sink_port; + forward_dropped_tcp2sink = 1; +} + +int slirp_should_forward_dropped_tcp2sink() { + return forward_dropped_tcp2sink; +} + +unsigned long slirp_get_tcp_sink_ip() { + return tcp_sink_ip; +} +int slirp_get_tcp_sink_port() { + return tcp_sink_port; +} + +/* Fill in the firewall rules. dst_lport and dst_hport are in host byte order */ +void slirp_add_allow(unsigned long dst_addr, + int dst_lport, int dst_hport, + u_int8_t proto) { + + struct fw_allow_entry** ate; + switch (proto) { + case IPPROTO_TCP: + ate = &allow_tcp_entries; + break; + case IPPROTO_UDP: + ate = &allow_udp_entries; + break; + default: + return; // unknown protocol for the FW + } + + while(*ate != NULL) + ate = &(*ate)->next; + + *ate = malloc(sizeof(**ate)); + if (*ate == NULL) { + DEBUG_MISC((dfd, + "Unable to create new firewall record, malloc failed\n")); + exit(-1); + } + + (*ate)->next = NULL; + (*ate)->dst_addr = dst_addr; + (*ate)->dst_lport = dst_lport; + (*ate)->dst_hport = dst_hport; +} + +void slirp_drop_log_fd(FILE* fd) { + drop_log_fd = fd; +} + +void slirp_dns_log_fd(FILE* fd) { + dns_log_fd = fd; +} + +/* Address and ports are in host byte order */ +int slirp_should_drop(unsigned long dst_addr, + int dst_port, + u_int8_t proto) { + + struct fw_allow_entry* ate; + + switch (proto) { + case IPPROTO_TCP: + if (drop_tcp != 0) + ate = allow_tcp_entries; + else + return 0; + break; + case IPPROTO_UDP: + if (drop_udp != 0) + ate = allow_udp_entries; + else + return 0; + break; + default: + return 1; // unknown protocol for the FW + } + + while(ate) { + if ((ate->dst_lport <= dst_port) && (dst_port <= ate->dst_hport)) { + // allow any destination if 0 + if (ate->dst_addr == 0 || ate->dst_addr == dst_addr) + return 0; + } + ate = ate->next; + } + + return 1; +} + +/* + * log DNS requests in a separate log + */ +int +slirp_log_dns(struct mbuf* m, int dropped) { + char dns_query[256]; // max allowable dns name size + int c = 0; + int i= 0; + int index = 0; + int offset = 40 + 1; // udp/ip headers length + 1; + int trim_bytes = 4; + + if (!dns_log_fd) + return -1; + + /* We assume one DNS name per query: 300 = 255 (max dns name length) + * + 40 (udp/ip hdr) + 1 byte DNS peamble + 4 bytes DNS suffix + */ + if (m->m_len < offset || m->m_len > 300) { + DEBUG_MISC((dfd,"Malformed DNS qeury, length %d \n", (int)m->m_len)); + return -1; + } + for (i = offset; i < m->m_len - trim_bytes && index < sizeof(dns_query); i++, index++) { + c = m->m_data[i]; + if (c < ' ' || c > '~') + c = '.'; + + dns_query[index] = (char)c; + } + dns_query[index] = '\0'; + if (!dropped) { + fprintf(dns_log_fd, "Sent DNS query for, %s\n" , dns_query); + } else { + fprintf(dns_log_fd, "Dropped DNS query for, %s\n" , dns_query); + } + fflush(dns_log_fd); + return 1; +} + +/* + * log DNS requests in a separate log + */ +int +slirp_dump_dns(struct mbuf* m) { + + if (!dns_log_fd) + return 0; + // first we write the length of the record then the record (IP packet) + if (!fwrite(&(m->m_len), sizeof(int), 1, dns_log_fd) || + !fwrite(m->m_data, m->m_len, 1, dns_log_fd)) { + return 0; + } + + fflush(dns_log_fd); + return 1; +} + +/* Log dropped/accepted packet info */ +int slirp_drop_log(const char* format, ...) { + va_list args; + + if (!drop_log_fd) + return 0; + + va_start(args, format); + vfprintf(drop_log_fd, format, args); + va_end(args); + + fflush(drop_log_fd); + + return 1; +} + + +/* Set max DNS requests allowed to be issued from the VM */ +void slirp_set_max_dns_conns(int num_conns) { + max_dns_conns = num_conns; +} + +int slirp_get_max_dns_conns() { + return max_dns_conns; +} + +/* generic guest network redirection functionality for ipv4 */ +struct net_forward_entry { + QTAILQ_ENTRY(net_forward_entry) next; + /* ip addresses are also in host byte order */ + unsigned long dest_ip; /* the destination address they try to contact */ + unsigned long dest_mask; /* the mask to apply to the address for matching */ + /* Range of ports they were trying to contact. In case of a single port, + * dest_lport should be the same as dest_hport */ + int dest_lport; /* Host byte order */ + int dest_hport; /* Host byte order */ + + unsigned long redirect_ip; + int redirect_port; /* Host byte order */ +}; + +static QTAILQ_HEAD(net_forwardq, net_forward_entry) net_forwards; + +static void slirp_net_forward_init(void) +{ + if (!slirp_net_forward_inited) { + QTAILQ_INIT(&net_forwards); + slirp_net_forward_inited = 1; + } +} + +/* all addresses and ports ae in host byte order */ +void slirp_add_net_forward(unsigned long dest_ip, unsigned long dest_mask, + int dest_lport, int dest_hport, + unsigned long redirect_ip, int redirect_port) +{ + slirp_net_forward_init(); + + struct net_forward_entry *entry = malloc(sizeof(*entry)); + if (entry == NULL) { + DEBUG_MISC((dfd, "Unable to create new forwarding entry, malloc failed\n")); + exit(-1); + } + + entry->dest_ip = dest_ip; + entry->dest_mask = dest_mask; + entry->dest_lport = dest_lport; + entry->dest_hport = dest_hport; + entry->redirect_ip = redirect_ip; + entry->redirect_port = redirect_port; + + QTAILQ_INSERT_TAIL(&net_forwards, entry, next); +} + +/* remote_port and redir_port arguments + * are in network byte order (tcp_subr.c) */ +int slirp_should_net_forward(unsigned long remote_ip, int remote_port, + unsigned long *redirect_ip, int *redirect_port) +{ + struct net_forward_entry *entry; + + for (entry = net_forwards.tqh_first; + entry != NULL; entry = entry->next.tqe_next) { + + if ((entry->dest_lport <= remote_port) + && (remote_port <= entry->dest_hport)) { + if ((entry->dest_ip & entry->dest_mask) + == (remote_ip & entry->dest_mask)) { + *redirect_ip = entry->redirect_ip; + *redirect_port = entry->redirect_port; + return 1; + } + } + } + + return 0; +} + +/*---------------------------------------------------*/ + + + + static void _slirp_redir_loop(void (*func)(void *opaque, int is_udp, const SockAddress *laddr, const SockAddress *faddr), diff --git a/slirp-android/socket.c b/slirp-android/socket.c index 44640a8..285a8ef 100644 --- a/slirp-android/socket.c +++ b/slirp-android/socket.c @@ -570,6 +570,26 @@ sosendto(struct socket *so, struct mbuf *m) addr_port = so->so_faddr_port; + /* + * test for generic forwarding; this function replaces the arguments + * only on success + */ + unsigned long faddr = addr_ip; + int fport = addr_port; + + if (slirp_should_net_forward(faddr, fport, &faddr, &fport)) { + slirp_drop_log( + "Redirected UDP: src: 0x%08lx:0x%04x org dst: 0x%08lx:0x%04x " + "new dst: 0x%08lx:0x%04x\n", + so->so_laddr_ip, so->so_laddr_port, + addr_ip, addr_port, + faddr, fport + ); + } + addr_ip = faddr; + addr_port = fport; + + sock_address_init_inet(&addr, addr_ip, addr_port); DEBUG_MISC((dfd, " sendto()ing, addr.sin_port=%d, addr.sin_addr.s_addr=%08x\n", addr_port, addr_ip)); @@ -779,4 +799,3 @@ sofwdrain(struct socket *so) else sofcantsendmore(so); } - diff --git a/slirp-android/tcp_subr.c b/slirp-android/tcp_subr.c index b8b680c..0550d4b 100644 --- a/slirp-android/tcp_subr.c +++ b/slirp-android/tcp_subr.c @@ -377,6 +377,29 @@ tcp_proxy_event( struct socket* so, tcp_input(NULL, sizeof(struct ip), so); } + +/* Tests if an IP address corresponds to a special Qemu service (eg, the DNS + * server or the gateway - see ctl.h) and if so, rewrites it with the real + * address of the service. + */ +int is_qemu_special_address(unsigned long dst_addr, unsigned long *redir_addr) +{ + if ((dst_addr & htonl(0xffffff00)) == special_addr_ip) { + /* It's an alias */ + + int last_byte = dst_addr & 0xff; + + if (CTL_IS_DNS(last_byte)) + *redir_addr = dns_addr[last_byte - CTL_DNS]; + else + *redir_addr = loopback_addr_ip; + + return 1; + } + return 0; +} + + /* * Connect to a host on the Internet * Called by tcp_input @@ -390,41 +413,56 @@ tcp_proxy_event( struct socket* so, int tcp_fconnect(struct socket *so) { int ret=0; - int try_proxy = 1; SockAddress sockaddr; - uint32_t sock_ip; - uint16_t sock_port; + unsigned long sock_ip; + int sock_port; DEBUG_CALL("tcp_fconnect"); DEBUG_ARG("so = %lx", (long )so); - sock_ip = so->so_faddr_ip; - sock_port = so->so_faddr_port; - - if ((sock_ip & 0xffffff00) == special_addr_ip) { - /* It's an alias */ - int last_byte = sock_ip & 0xff; - - if (CTL_IS_DNS(last_byte)) - sock_ip = dns_addr[last_byte - CTL_DNS]; - else - sock_ip = loopback_addr_ip; - try_proxy = 0; - } - - sock_address_init_inet( &sockaddr, sock_ip, sock_port ); - - DEBUG_MISC((dfd, " connect()ing, addr=%s, proxy=%d\n", - sock_address_to_string(&sockaddr), try_proxy)); - - if (try_proxy) { - if (!proxy_manager_add(&sockaddr, SOCKET_STREAM, (ProxyEventFunc) tcp_proxy_event, so)) { - soisfconnecting(so); - so->s = -1; - so->so_state |= SS_PROXIFIED; - return 0; - } + /* when true, a connection that otherwise would be dropped will instead be + * redirected to the sink ('-net-forward-tcp2sink') */ + int forward_dropped_to_sink = 0; + + + /*-------------------------------------------------------------*/ + /* User mode network stack restrictions */ + if (slirp_should_drop(so->so_faddr_ip, so->so_faddr_port, IPPROTO_TCP)) { + + /* If forwarding to sink is enabled, don't actually drop it */ + if (slirp_should_forward_dropped_tcp2sink()) { + slirp_drop_log( + "About to be dropped TCP forwarded to sink: " + "src: 0x%08lx:0x%04x dst: 0x%08lx:0x%04x\n", + so->so_laddr_ip, + so->so_laddr_port, + so->so_faddr_ip, + so->so_faddr_port + ); + forward_dropped_to_sink = 1; + } + else { + slirp_drop_log( + "Dropped TCP: src: 0x%08lx:0x%04x dst: 0x%08lx:0x%04x\n", + so->so_laddr_ip, + so->so_laddr_port, + so->so_faddr_ip, + so->so_faddr_port + ); + //errno = ENETUNREACH; + errno = ECONNREFUSED; + return -1; + } + } else { + slirp_drop_log( + "Allowed TCP: src: 0x%08lx:0x%04x dst: 0x%08lx:0x%04x\n", + so->so_laddr_ip, + so->so_laddr_port, + so->so_faddr_ip, + so->so_faddr_port + ); } + /*-------------------------------------------------------------*/ if ((ret=so->s=socket_create_inet(SOCKET_STREAM)) >= 0) { @@ -434,6 +472,79 @@ int tcp_fconnect(struct socket *so) socket_set_xreuseaddr(s); socket_set_oobinline(s); + + if (forward_dropped_to_sink) { + + /* This connection would normally be dropped, but since forwarding of + * dropped connections is enabled, redirect it to the sink */ + sock_ip = slirp_get_tcp_sink_ip(); + sock_port= slirp_get_tcp_sink_port(); + slirp_drop_log( + "Redirected would-be dropped TCP to sink: " + "src: 0x%08lx:0x%04x org dst: 0x%08lx:0x%04x " + "new dst: 0x%08lx:0x%04x\n", + so->so_laddr_ip, so->so_laddr_port, + so->so_faddr_ip, so->so_faddr_port, + sock_ip, sock_port + ); + } + else { /* An allowed connection */ + + unsigned long faddr; + int fport; + + /* Determine if the connection should be redirected + * due to a -net-forward rule */ + /* faddr and fport are modified only on success */ + if (slirp_should_net_forward(so->so_faddr_ip, so->so_faddr_port, + &faddr, &fport)) { + slirp_drop_log( + "Redirected TCP: src: 0x%08lx:0x%04x org dst: 0x%08lx:0x%04x " + "new dst: 0x%08lx:0x%04x\n", + so->so_laddr_ip, so->so_laddr_port, + so->so_faddr_ip, so->so_faddr_port, + faddr, fport + ); + sock_ip = faddr; /* forced dst addr */ + sock_port= fport; /* forced dst port */ + } + /* Determine if this is a connection to a special qemu service, + * and change the destination address accordingly. + * 'faddr' is modified only onsuccess */ + else if (is_qemu_special_address(so->so_faddr_ip, &faddr)) { + + /* We keep the original destination port. If a special service + * listens on a different port than the standard, then appropriate + * forwarding should be set up using -net-forward, e.g., as it is + * the case with Mawler's DNS traffic, which is redirected to the + * special DNS port: + * -net-forward 0.0.0.0:0.0.0.0:53:127.0.0.1:21737 */ + + sock_ip = faddr; /* real DNS/gateway addr */ + sock_port= so->so_faddr_port;/* original dst port */ + + } + /* A normal connection - keep the original destination addr/port */ + else { + + if (!proxy_manager_add(&sockaddr, SOCKET_STREAM, + (ProxyEventFunc) tcp_proxy_event, so)) { + soisfconnecting(so); + so->s = -1; + so->so_state |= SS_PROXIFIED; + return 0; + } + + sock_ip = so->so_faddr_ip; /* original dst addr */ + sock_port= so->so_faddr_port; /* original dst port */ + } + } + + DEBUG_MISC((dfd, " connect()ing, addr=%s, proxy=%d\n", + sock_address_to_string(&sockaddr), try_proxy)); + + sock_address_init_inet( &sockaddr, sock_ip, sock_port ); + /* We don't care what port we get */ socket_connect(s, &sockaddr); diff --git a/slirp-android/udp.c b/slirp-android/udp.c index 9091505..a9c9a82 100644 --- a/slirp-android/udp.c +++ b/slirp-android/udp.c @@ -47,6 +47,10 @@ struct udpstat udpstat; #endif +/* Keeps track of the number of DNS requests. Used to implement the firewall + * option that restricts the number of DNS requests (-max_dns_conns). */ +u_int dns_num_conns; + struct socket udb; static u_int8_t udp_tos(struct socket *so); @@ -68,6 +72,7 @@ void udp_init(void) { udb.so_next = udb.so_prev = &udb; + dns_num_conns = 0; } /* m->m_data points at ip packet header * m->m_len length ip packet @@ -121,6 +126,33 @@ udp_input(register struct mbuf *m, int iphlen) ip->ip_len = len; } + /* ------------------------------------------------------*/ + /* User mode network stack restrictions */ + /* slirp_should_drop requires host byte ordering in arguments */ + if (slirp_should_drop(ntohl(ip->ip_dst.addr), ntohs(uh->uh_dport.port), + IPPROTO_UDP)) { + slirp_drop_log( + "Dropped UDP: src: 0x%08lx:0x%04x dst: 0x%08lx:0x%04x\n", + ip->ip_src.addr, + uh->uh_sport.port, + ip->ip_dst.addr, + uh->uh_dport.port + ); + goto bad; /* drop the packet */ + } + else { + slirp_drop_log( + "Allowed UDP: src: 0x%08lx:0x%04x dst: 0x%08lx:0x%04x\n", + ip->ip_src.addr, + uh->uh_sport.port, + ip->ip_dst.addr, + uh->uh_dport.port + ); + } + /* ------------------------------------------------------*/ + + + /* * Save a copy of the IP header in case we want restore it * for sending an ICMP error message in response. @@ -164,6 +196,18 @@ udp_input(register struct mbuf *m, int iphlen) goto bad; } + // DNS logging and FW rules + if (ntohs(uh->uh_dport.port) == 53) { + if (!slirp_dump_dns(m)) { + DEBUG_MISC((dfd,"Error logging DNS packet")); + } + dns_num_conns++; + if (slirp_get_max_dns_conns() != -1 && + dns_num_conns > slirp_get_max_dns_conns()) + goto bad; + } + + /* * Locate pcb for datagram. */ @@ -309,6 +353,13 @@ int udp_output2_(struct socket *so, struct mbuf *m, STAT(udpstat.udps_opackets++); + // DNS logging + if (so != NULL && so->so_faddr_port == htons(53)) { + if (!slirp_dump_dns(m)) { + DEBUG_MISC((dfd,"Error logging DNS packet")); + } + } + error = ip_output(so, m); return (error); diff --git a/slirp-android/udp.h b/slirp-android/udp.h index 4350ec3..2224181 100644 --- a/slirp-android/udp.h +++ b/slirp-android/udp.h @@ -69,7 +69,6 @@ struct udpiphdr { #define ui_ulen ui_u.uh_ulen #define ui_sum ui_u.uh_sum -#ifdef LOG_ENABLED struct udpstat { /* input statistics: */ u_long udps_ipackets; /* total input packets */ @@ -83,7 +82,6 @@ struct udpstat { /* output statistics: */ u_long udps_opackets; /* total output packets */ }; -#endif /* * Names for UDP sysctl objects @@ -91,9 +89,7 @@ struct udpstat { #define UDPCTL_CHECKSUM 1 /* checksum UDP packets */ #define UDPCTL_MAXID 2 -#ifdef LOG_ENABLED extern struct udpstat udpstat; -#endif extern struct socket udb; struct mbuf; -- cgit v1.1