From patchwork Sat Apr 16 01:38:18 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daisuke Nojiri X-Patchwork-Id: 91465 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 53662B6FB6 for ; Sat, 16 Apr 2011 11:38:42 +1000 (EST) Received: from localhost ([::1]:58612 helo=lists2.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QAuT8-0002XG-4G for incoming@patchwork.ozlabs.org; Fri, 15 Apr 2011 21:38:38 -0400 Received: from eggs.gnu.org ([140.186.70.92]:45861) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QAuSv-0002XB-Nm for qemu-devel@nongnu.org; Fri, 15 Apr 2011 21:38:27 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QAuSt-0002Ek-GD for qemu-devel@nongnu.org; Fri, 15 Apr 2011 21:38:25 -0400 Received: from smtp-out.google.com ([74.125.121.67]:59298) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QAuSs-0002Ee-P2 for qemu-devel@nongnu.org; Fri, 15 Apr 2011 21:38:23 -0400 Received: from wpaz21.hot.corp.google.com (wpaz21.hot.corp.google.com [172.24.198.85]) by smtp-out.google.com with ESMTP id p3G1cKXe014160 for ; Fri, 15 Apr 2011 18:38:21 -0700 DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=google.com; s=beta; t=1302917901; bh=w+q+303mrKgwMyZBNbR+FI03vls=; h=MIME-Version:In-Reply-To:References:Date:Message-ID:Subject:From: To:Content-Type; b=XP8lKV+cyo/2PRhkJCDMspFQsnzf796yzQjmsPqAGtM5ArUULeDcwbL1vdaUSBT9O ktCUhtJAlIF7nMRtzHm4w== Received: from pvg4 (pvg4.prod.google.com [10.241.210.132]) by wpaz21.hot.corp.google.com with ESMTP id p3G1cIxn023876 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Fri, 15 Apr 2011 18:38:19 -0700 Received: by pvg4 with SMTP id 4so1880151pvg.14 for ; Fri, 15 Apr 2011 18:38:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=beta; h=domainkey-signature:mime-version:in-reply-to:references:date :message-id:subject:from:to:content-type; bh=q1j/CESHKe8AvZoIuIhpFgPt7mblEr7y9boaUCIRpgw=; b=aahSg2C1f+VRtPih8DdD4C3O8n+ivX4lbY0gI9RyPW1rt7tYmgOxF9hXue1873z/M5 mhF0XR/f/WdQGLxgB1cg== DomainKey-Signature: a=rsa-sha1; c=nofws; d=google.com; s=beta; h=mime-version:in-reply-to:references:date:message-id:subject:from:to :content-type; b=EvZJhNQSnkzuPKFQoXGclBXrf16hs9rnnVJndcyGNqSfzDd9eh8SYVbev0Y7vpp33O xVNY4W1jSvDEh9daPL6Q== MIME-Version: 1.0 Received: by 10.143.21.27 with SMTP id y27mr1125919wfi.115.1302917898468; Fri, 15 Apr 2011 18:38:18 -0700 (PDT) Received: by 10.142.188.11 with HTTP; Fri, 15 Apr 2011 18:38:18 -0700 (PDT) In-Reply-To: References: Date: Fri, 15 Apr 2011 18:38:18 -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: 74.125.121.67 Subject: Re: [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. 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..51e4728 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,98 @@ 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 = qemu_strdup(str); + + p = rindex(arg, ']'); + if ((*arg == '[') & (p != NULL)) { + p = arg + 1; /* skip '[' */ + low = atoi(strsep(&p, "-")); + if (p == NULL) { /* '-' is not found. e.g. [port] */ + high = low; + } else { /* [port-port] */ + high = atoi(p); + } + } else { + low = atoi(arg); + high = low; + } + + qemu_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, + "Invalid argument: %s. " + "Protocol name is missing or not recognized.\n", + optarg); + 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. " + "proto:dst_addr:dst_port or " + "proto: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: %s.\n", + optarg); + 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); } /*