From patchwork Fri Apr 15 23:03:52 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daisuke Nojiri X-Patchwork-Id: 91458 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [140.186.70.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 7FE21B7012 for ; Sat, 16 Apr 2011 09:04:19 +1000 (EST) Received: from localhost ([::1]:33348 helo=lists2.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QAs3i-00042L-Cz for incoming@patchwork.ozlabs.org; Fri, 15 Apr 2011 19:04:14 -0400 Received: from eggs.gnu.org ([140.186.70.92]:42572) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QAs3R-000423-LJ for qemu-devel@nongnu.org; Fri, 15 Apr 2011 19:03:59 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QAs3P-00071V-Bo for qemu-devel@nongnu.org; Fri, 15 Apr 2011 19:03:57 -0400 Received: from smtp-out.google.com ([216.239.44.51]:59189) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QAs3O-00071P-UV for qemu-devel@nongnu.org; Fri, 15 Apr 2011 19:03:55 -0400 Received: from wpaz24.hot.corp.google.com (wpaz24.hot.corp.google.com [172.24.198.88]) by smtp-out.google.com with ESMTP id p3FN3sx1022825 for ; Fri, 15 Apr 2011 16:03:54 -0700 DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=google.com; s=beta; t=1302908634; bh=7p7XqT4bPzkTkRXD1GGXdj+grmM=; h=MIME-Version:Date:Message-ID:Subject:From:To:Content-Type; b=j+rtcFD8yxQcncdHV6CQzbcX1Q1wCSBjk23cTjp5w9IHcm/nVlmxxOGDIa6LTF7b2 PJ2MLlCLmCeIBjXudV0fw== Received: from pvg3 (pvg3.prod.google.com [10.241.210.131]) by wpaz24.hot.corp.google.com with ESMTP id p3FN3FbM020909 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Fri, 15 Apr 2011 16:03:53 -0700 Received: by pvg3 with SMTP id 3so1808211pvg.18 for ; Fri, 15 Apr 2011 16:03:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=beta; h=domainkey-signature:mime-version:date:message-id:subject:from:to :content-type; bh=tVtKHh66IE+PQ2xpyQNPzHZGuPTX3QQvfX8JfbLRaoA=; b=bXpyHrM+lLnK6AGrCz/asDJv7gUr5WDmGsjClk0YWlvpshZcEY6QxgIRmXef4gzZDJ NV7wIDQ3qBb/Q0D1NMaQ== DomainKey-Signature: a=rsa-sha1; c=nofws; d=google.com; s=beta; h=mime-version:date:message-id:subject:from:to:content-type; b=UDxoL8b5SSjc2aldMKTufoTbZ1JscnuuA7XVCBWSJkq/FSDVz+EMABaW4GQJOXEcQ5 tYDe8LfjYhgUF7t7JVyg== MIME-Version: 1.0 Received: by 10.142.117.21 with SMTP id p21mr1039332wfc.286.1302908632928; Fri, 15 Apr 2011 16:03:52 -0700 (PDT) Received: by 10.142.188.11 with HTTP; Fri, 15 Apr 2011 16:03:52 -0700 (PDT) Date: Fri, 15 Apr 2011 16:03:52 -0700 Message-ID: From: Daisuke Nojiri To: qemu-devel@nongnu.org, Blue Swirl , Jan Kiszka X-System-Of-Record: true X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-Received-From: 216.239.44.51 Subject: [Qemu-devel] [PATCH 3/4] Slirp Reverse UDP Firewall X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This patch series adds a simple reverse UDP firewall functionality to Slirp. The series consists of four patches: 1. drop=udp|all - enables the firewall 2. droplog=FILE - sets the drop log filename 3. allow=PROTO:ADDR:PORT - adds an allow rule 4. parse network mask (e.g. /24) for ADDR e.g.) $ qemu -net user,drop=udp,droplog=qemu.drop,allow=udp:10.0.2.3:53 All UDP packets except ones allowed by allow rules will be dropped. The source and the destination of the dropped packets are logged in the file specified by FILE. PORT can be a single number (e.g. 53) or a range (e.g. [80-81]). ADDR can be a single address (e.g. 1.2.3.4) or a range (e.g. 1.2.3.4/24). If ADDR is ommitted, all addresses match the rule. If PROTO is omitted, all protocols match the rule. TCP support will follow in another patch series. Signed-off-by: Daisuke Nojiri diff --git a/net.c b/net.c index 0707188..35ec2ae 100644 --- a/net.c +++ b/net.c @@ -933,6 +933,10 @@ static const struct { .name = "droplog", .type = QEMU_OPT_STRING, .help = "Set log filename for the reverse firewall", + }, { + .name = "allow", + .type = QEMU_OPT_STRING, + .help = "Add an allow rule for the reverse firewall", }, { /* end of list */ } }, diff --git a/net/slirp.c b/net/slirp.c index 07e1353..5a1fdcc 100644 --- a/net/slirp.c +++ b/net/slirp.c @@ -34,6 +34,12 @@ #include "qemu_socket.h" #include "slirp/libslirp.h" +int slirp_add_allow(Slirp *slirp, const char *optarg); + +int parse_port_range(const char *str, + unsigned short *lport, + unsigned short *hport); + static int get_str_sep(char *buf, int buf_size, const char **pp, int sep) { const char *p, *p1; @@ -58,6 +64,7 @@ static int get_str_sep(char *buf, int buf_size, const char **pp, int sep) #define SLIRP_CFG_HOSTFWD 1 #define SLIRP_CFG_LEGACY 2 +#define SLIRP_CFG_ALLOW 4 struct slirp_config_str { struct slirp_config_str *next; @@ -255,6 +262,9 @@ static int net_slirp_init(VLANState *vlan, const char *model, if (slirp_hostfwd(s, config->str, config->flags & SLIRP_CFG_LEGACY) < 0) goto error; + } else if (config->flags & SLIRP_CFG_ALLOW) { + if (slirp_add_allow(s->slirp, config->str)) + goto error; } else { if (slirp_guestfwd(s, config->str, config->flags & SLIRP_CFG_LEGACY) < 0) @@ -659,7 +669,9 @@ static int net_init_slirp_configs(const char *name, const char *value, void *opa { struct slirp_config_str *config; - if (strcmp(name, "hostfwd") != 0 && strcmp(name, "guestfwd") != 0) { + if (strcmp(name, "hostfwd") != 0 && + strcmp(name, "guestfwd") != 0 && + strcmp(name, "allow") != 0) { return 0; } @@ -669,6 +681,8 @@ static int net_init_slirp_configs(const char *name, const char *value, void *opa if (!strcmp(name, "hostfwd")) { config->flags = SLIRP_CFG_HOSTFWD; + } else if (!strcmp(name, "allow")) { + config->flags = SLIRP_CFG_ALLOW; } config->next = slirp_configs; @@ -795,3 +809,89 @@ int net_slirp_parse_legacy(QemuOptsList *opts_list, const char *optarg, int *ret return 1; } + +/* + * Parse a null-terminated string specifying a network port or port range (e.g. + * "[1024-65535]"). In case of a single port, lport and hport are the same. + * Returns 0 on success and -1 on error. + */ +int parse_port_range(const char *str, + unsigned short *lport, + unsigned short *hport) { + unsigned int low = 0, high = 0; + char *p, *arg = strdup(str); + + p = rindex(arg, ']'); + if ((*arg == '[') & (p != NULL)) { + p = arg + 1; /* skip '[' */ + low = atoi(strsep(&p, "-")); + high = atoi(p); + } else { + low = atoi(arg); + high = low; + } + + free(arg); + + if ((low > 0) && (high > 0) && (low <= high) && (high < 65536)) { + *lport = low; + *hport = high; + return 0; + } + + return -1; +} + +/* + * Add allow rules for the usermode firewall. + */ +int slirp_add_allow(Slirp *slirp, const char *optarg) +{ + /* + * we expect the following format: + * dst_addr:dst_port OR dst_addr:[dst_lport-dst_hport] + */ + char *argument = strdup(optarg), *p = argument; + char *dst_addr_str, *dst_port_str; + struct in_addr dst_addr; + unsigned short dst_lport, dst_hport; + char *proto_str; + u_int8_t proto; + + proto_str = strsep(&p, ":"); + if (!strcmp(proto_str, "udp")) { + proto = IPPROTO_UDP; + } else { + fprintf(stderr, + "Unknown protocol in a rule for the reverse firewall\n"); + return -1; + } + dst_addr_str = strsep(&p, ":"); + dst_port_str = p; + + if (dst_addr_str == NULL || dst_port_str == NULL) { + fprintf(stderr, + "Invalid argument %s for -allow. We expect " + "dst_addr:dst_port or dst_addr:[dst_lport-dst_hport]\n", + optarg); + return -1; + } + + /* handling ":port" notation (when IP address is omitted entirely). */ + if (*dst_addr_str == '\0') { + dst_addr.s_addr = 0; + } else if (inet_aton(dst_addr_str, &dst_addr) == 0) { + fprintf(stderr, "Invalid destination IP address: %s\n", dst_addr_str); + return -1; + } + + if (parse_port_range(dst_port_str, &dst_lport, &dst_hport) == -1) { + fprintf(stderr, "Invalid destination port or port range\n"); + return -1; + } + + slirp_add_allow_internal(slirp, dst_addr, dst_lport, dst_hport, proto); + + free(argument); + return 0; +} diff --git a/qemu-options.hx b/qemu-options.hx index 7a8872b..b536f82 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1067,9 +1067,10 @@ DEF("net", HAS_ARG, QEMU_OPTION_net, #ifdef CONFIG_SLIRP "-net user[,vlan=n][,name=str][,net=addr[/mask]][,host=addr][,restrict=y|n]\n" " [,hostname=host][,dhcpstart=addr][,dns=addr][,tftp=dir][,bootfile=f]\n" - " [,hostfwd=rule][,guestfwd=rule][,drop=udp|all][,droplog=file]" + " [,hostfwd=rule][,guestfwd=rule][,drop=udp|all][,droplog=file]\n" + " [,allow=rule]\n" #ifndef _WIN32 - "[,smb=dir[,smbserver=addr]]\n" + " [,smb=dir[,smbserver=addr]]\n" #endif " connect the user mode network stack to VLAN 'n', configure its\n" " DHCP server and enabled optional services\n" diff --git a/slirp/libslirp.h b/slirp/libslirp.h index f1e48a7..1f4ddb1 100644 --- a/slirp/libslirp.h +++ b/slirp/libslirp.h @@ -54,6 +54,13 @@ int slirp_should_drop(Slirp *slirp, unsigned short dst_port, u_int8_t proto); +/* slirp.c */ +void slirp_add_allow_internal(Slirp *slirp, + struct in_addr dst_addr, + unsigned short dst_lport, + unsigned short dst_hport, + u_int8_t proto); + #else /* !CONFIG_SLIRP */ static inline void slirp_select_fill(int *pnfds, fd_set *readfds, diff --git a/slirp/slirp.c b/slirp/slirp.c index 81fd85b..928025e 100644 --- a/slirp/slirp.c +++ b/slirp/slirp.c @@ -233,6 +233,7 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork, slirp->drop = drop; slirp->drop_log = drop_log; + QSIMPLEQ_INIT(&(slirp->udp_allows)); slirp->opaque = opaque; @@ -1126,20 +1127,64 @@ int slirp_should_drop(Slirp *slirp, struct in_addr dst_addr, unsigned short dst_port, u_int8_t proto) { + /* struct rfw_allow *allows = NULL; */ + QSIMPLEQ_HEAD(rfw_allows, rfw_allow) *allows; + struct rfw_allow *allow; + unsigned short dport; /* host byte order */ + switch (proto) { case IPPROTO_UDP: if (!(slirp->drop & SLIRP_DROP_UDP)) { return 0; + } else { + allows = (struct rfw_allows*)&slirp->udp_allows; } break; default: - return 0; /* unrecognized protocol. default pass. */ + return 1; /* unrecognized protocol. default drop. */ } + /* Find matching allow rule. 0 works as a wildcard for address. */ + QSIMPLEQ_FOREACH(allow, allows, next) { + dport = ntohs(dst_port); + if ((allow->dst_lport <= dport) && (dport <= allow->dst_hport)) { + /* allow any destination if 0 */ + if (allow->dst_addr.s_addr == 0 + || allow->dst_addr.s_addr == dst_addr.s_addr) { + return 0; + } + } + } return 1; } /* + * Register an allow rule for user mode firewall + */ +void slirp_add_allow_internal(Slirp *slirp, + struct in_addr dst_addr, + unsigned short dst_lport, + unsigned short dst_hport, + u_int8_t proto) { + struct rfw_allow *allow = qemu_malloc(sizeof(struct rfw_allow)); + + allow->dst_addr = dst_addr; + allow->dst_lport = dst_lport; + allow->dst_hport = dst_hport; + + switch (proto) { + case IPPROTO_UDP: + QSIMPLEQ_INSERT_HEAD(&(slirp->udp_allows), allow, next); + break; + case IPPROTO_TCP: + default: + qemu_free(allow); + return; /* unrecognized protocol */ + } +} + + +/* * Write to drop-log */ int slirp_drop_log(FILE *drop_log, const char *format, ...) diff --git a/slirp/slirp.h b/slirp/slirp.h index d95953c..619bbee 100644 --- a/slirp/slirp.h +++ b/slirp/slirp.h @@ -170,6 +170,16 @@ int inet_aton(const char *cp, struct in_addr *ia); int qemu_socket(int domain, int type, int protocol); +struct rfw_allow { + /* struct rfw_allow *next; */ + QSIMPLEQ_ENTRY(rfw_allow) next; + + struct in_addr dst_addr; + /* Port range. For a single port, dst_lport = dst_hport. */ + unsigned short dst_lport; /* in host byte order */ + unsigned short dst_hport; /* in host byte order */ +}; + struct Slirp { QTAILQ_ENTRY(Slirp) entry; @@ -183,6 +193,7 @@ struct Slirp { /* Reverse Firewall configuration */ unsigned char drop; FILE *drop_log; + QSIMPLEQ_HEAD(rfw_allows, rfw_allow) udp_allows; /* ARP cache for the guest IP addresses (XXX: allow many entries) */ uint8_t client_ethaddr[6]; @@ -303,6 +314,7 @@ int tcp_emu(struct socket *, struct mbuf *); int tcp_ctl(struct socket *); struct tcpcb *tcp_drop(struct tcpcb *tp, int err); + #ifdef USE_PPP #define MIN_MRU MINMRU #define MAX_MRU MAXMRU diff --git a/slirp/udp.c b/slirp/udp.c index 6519d36..5c602d1 100644 --- a/slirp/udp.c +++ b/slirp/udp.c @@ -116,7 +116,14 @@ udp_input(register struct mbuf *m, int iphlen) timestamp); goto bad; } else { - /* PASS */ + slirp_drop_log( + slirp->drop_log, + "Allowed UDP: src:0x%08x:0x%04hx dst:0x%08x:0x%04hx %ld\n", + ntohl(ip->ip_src.s_addr), + ntohs(uh->uh_sport), + ntohl(ip->ip_dst.s_addr), + ntohs(uh->uh_dport), + timestamp); } /*