This patch series adds a simple reverse UDP firewall functionality to Slirp.
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
All UDP packets except ones allowed by allow rules will be dropped.
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
If PROTO is omitted, all protocols match the rule.
TCP support will follow in another patch series.
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);
}
/*