Patchwork [3/3] Slirp Reverse UDP Firewall

login
register
mail settings
Submitter Daisuke Nojiri
Date April 13, 2011, 12:56 a.m.
Message ID <BANLkTins80rpiLGBh7W9UCkmMSGTxEBOaA@mail.gmail.com>
Download mbox | patch
Permalink /patch/90904/
State New
Headers show

Comments

Daisuke Nojiri - April 13, 2011, 12:56 a.m.
This patch series adds a reverse UDP firewall functionality to Slirp.
The series consists of three patches. Each adds one -net user option:

    1. dropudp=y|n - enables the firewall
    2. droplog=FILE - sets the drop log filename
    3. allow=udp:ADDR:PORT - adds an allow rule

  e.g.) $ qemu -net user,dropudp=y,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]). If ADDR is
ommitted, all addresses match the rule.

TCP support will follow (in another patch series).

Signed-off-by: Daisuke Nojiri <dnojiri@google.com>

                 ntohl(ip->ip_src.s_addr),
@@ -114,7 +113,13 @@ udp_input(register struct mbuf *m, int iphlen)
                 timestamp);
             goto bad;
         } else {
-            /* PASS */
+            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);
         }

  /*

Patch

diff --git a/net.c b/net.c
index 38ca29a..a1048ad 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 38995ec..047edc2 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -58,6 +58,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;
@@ -254,6 +255,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(config->str))
+                goto error;
         } else {
             if (slirp_guestfwd(s, config->str,
                                config->flags & SLIRP_CFG_LEGACY) < 0)
@@ -658,7 +662,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;
     }

@@ -668,6 +674,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;
diff --git a/qemu-options.hx b/qemu-options.hx
index e59bf93..e1143f3 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1067,9 +1067,9 @@  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][,dropudp=y|n][,droplog=file]"
+    "
[,hostfwd=rule][,guestfwd=rule][,dropudp=y|n][,droplog=file][,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 a389cc5..4463862 100644
--- a/slirp/libslirp.h
+++ b/slirp/libslirp.h
@@ -47,6 +47,7 @@  size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr
guest_addr,
 /* Usermode firewall functions */
 void slirp_enable_drop_udp(void);
 void slirp_set_drop_log_fd(FILE *fd);
+int slirp_add_allow(const char *optarg);
 int slirp_drop_log(const char *format, ...);
 int slirp_should_drop(unsigned long dst_addr,
                       unsigned short dst_port,
diff --git a/slirp/slirp.c b/slirp/slirp.c
index 29dd0ca..a6e2bc0 100644
--- a/slirp/slirp.c
+++ b/slirp/slirp.c
@@ -1113,10 +1113,22 @@  static int slirp_state_load(QEMUFile *f, void
*opaque, int version_id)
 }

 /*
+ * Allow rule for the usermode firewall
+ */
+struct ufw_allowed {
+    struct ufw_allowed *next;
+    unsigned long 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 */
+};
+
+/*
  * Global variables for the usermode firewall
  */
 static int drop_udp = 0;
 static FILE *drop_log_fd = NULL;
+static struct ufw_allowed *fw_allowed_udp = NULL;

 void slirp_enable_drop_udp(void)
 {
@@ -1131,21 +1143,156 @@  void slirp_set_drop_log_fd(FILE *fd)
 int slirp_should_drop(unsigned long dst_addr,
                       unsigned short dst_port,
                       u_int8_t proto) {
+    struct ufw_allowed *fwa = NULL;
+    unsigned short dport;   /* host byte order */
+
     switch (proto) {
     case IPPROTO_UDP:
         if (drop_udp == 0) {
             return 0;
+        } else {
+            fwa = fw_allowed_udp;
         }
         break;
     case IPPROTO_TCP:
     default:
-        return 0;   /* unrecognized protocol. default pass. */
+        return 1;   /* unrecognized protocol. default drop. */
     }

+    /* Find matching allow rule. 0 works as a wildcard for address. */
+    for (; fwa; fwa = fwa->next) {
+        dport = ntohs(dst_port);
+        if ((fwa->dst_lport <= dport) && (dport <= fwa->dst_hport)) {
+            /* allow any destination if 0 */
+            if (fwa->dst_addr == 0 || fwa->dst_addr == dst_addr) {
+                return 0;
+            }
+        }
+    }
     return 1;
 }

 /*
+ * Register an allow rule for user mode firewall
+ */
+void slirp_add_allow_internal(unsigned long dst_addr,
+                              unsigned short dst_lport,
+                              unsigned short dst_hport,
+                              u_int8_t proto) {
+    struct ufw_allowed *fwa;
+
+    fwa = (struct ufw_allowed *)malloc(sizeof(struct ufw_allowed));
+    if (!fwa) {
+        DEBUG_MISC((dfd, "Unabled to create a new firewall rule "
+                    "due to malloc failure\n"));
+        exit(-1);
+    }
+
+    fwa->dst_addr = dst_addr;
+    fwa->dst_lport = dst_lport;
+    fwa->dst_hport = dst_hport;
+
+    switch (proto) {
+    case IPPROTO_UDP:
+        fwa->next = fw_allowed_udp;
+        fw_allowed_udp = fwa;
+        break;
+    case IPPROTO_TCP:
+    default:
+        free(fwa);
+        return;   /* unrecognized protocol */
+    }
+}
+
+/*
+ * 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(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;
+    }
+    /* inet_aton returns 0 on failure. */
+    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(dst_addr.s_addr, dst_lport, dst_hport, proto);
+
+    free(argument);
+    return 0;
+}
+
+/*
  * Write to drop-log
  */
 int slirp_drop_log(const char *format, ...)
diff --git a/slirp/slirp.h b/slirp/slirp.h
index 954289a..29ee425 100644
--- a/slirp/slirp.h
+++ b/slirp/slirp.h
@@ -299,6 +299,15 @@  int tcp_emu(struct socket *, struct mbuf *);
 int tcp_ctl(struct socket *);
 struct tcpcb *tcp_drop(struct tcpcb *tp, int err);

+/* slirp.c */
+void slirp_add_allow_internal(unsigned long dst_addr,
+                              unsigned short dst_lport,
+                              unsigned short dst_hport,
+                              u_int8_t proto);
+int parse_port_range(const char *str,
+                     unsigned short *lport,
+                     unsigned short *hport);
+
 #ifdef USE_PPP
 #define MIN_MRU MINMRU
 #define MAX_MRU MAXMRU
diff --git a/slirp/udp.c b/slirp/udp.c
index f92731c..db7e4ca 100644
--- a/slirp/udp.c
+++ b/slirp/udp.c
@@ -104,7 +104,6 @@  udp_input(register struct mbuf *m, int iphlen)
          * User mode firewall
          */
         if (slirp_should_drop(ip->ip_dst.s_addr, uh->uh_dport,
IPPROTO_UDP)) {
-            /* DROP */
             slirp_drop_log(
                 "Dropped UDP: src:0x%08x:0x%04hx dst:0x%08x:0x%04hx %ld\n",