Patchwork [3/4] Slirp Reverse UDP Firewall

login
register
mail settings
Submitter Daisuke Nojiri
Date April 15, 2011, 11:03 p.m.
Message ID <BANLkTi=hZCFD5Ri56-HJigiZKYxtQMmCkg@mail.gmail.com>
Download mbox | patch
Permalink /patch/91458/
State New
Headers show

Comments

Daisuke Nojiri - April 15, 2011, 11:03 p.m.
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 <dnojiri@google.com>

Patch

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);
         }

  /*