add filtering
[l2tpns.git] / l2tpns.c
index 4bdf862..a1f30a2 100644 (file)
--- a/l2tpns.c
+++ b/l2tpns.c
@@ -4,7 +4,7 @@
 // Copyright (c) 2002 FireBrick (Andrews & Arnold Ltd / Watchfront Ltd) - GPL licenced
 // vim: sw=8 ts=8
 
-char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.51 2004/11/17 08:23:34 bodea Exp $";
+char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.59 2004/11/28 20:10:04 bodea Exp $";
 
 #include <arpa/inet.h>
 #include <assert.h>
@@ -54,7 +54,7 @@ char const *cvs_id_l2tpns = "$Id: l2tpns.c,v 1.51 2004/11/17 08:23:34 bodea Exp
 #endif /* BGP */
 
 // Globals
-struct configt *config = NULL; // all configuration
+configt *config = NULL;                // all configuration
 int tunfd = -1;                        // tun interface file handle. (network device)
 int udpfd = -1;                        // UDP file handle
 int controlfd = -1;            // Control signal handle
@@ -88,9 +88,9 @@ linked_list *loaded_plugins;
 linked_list *plugins[MAX_PLUGIN_TYPES];
 
 #define membersize(STRUCT, MEMBER) sizeof(((STRUCT *)0)->MEMBER)
-#define CONFIG(NAME, MEMBER, TYPE) { NAME, offsetof(struct configt, MEMBER), membersize(struct configt, MEMBER), TYPE }
+#define CONFIG(NAME, MEMBER, TYPE) { NAME, offsetof(configt, MEMBER), membersize(configt, MEMBER), TYPE }
 
-struct config_descriptt config_values[] = {
+config_descriptt config_values[] = {
        CONFIG("debug", debug, INT),
        CONFIG("log_file", log_filename, STRING),
        CONFIG("pid_file", pid_file, STRING),
@@ -146,6 +146,7 @@ sessiont *session = NULL;           // Array of session structures.
 sessioncountt *sess_count = NULL;      // Array of partial per-session traffic counters.
 radiust *radius = NULL;                        // Array of radius structures.
 ippoolt *ip_address_pool = NULL;       // Array of dynamic IP addresses.
+ip_filtert *ip_filters = NULL; // Array of named filters.
 static controlt *controlfree = 0;
 struct Tstats *_statistics = NULL;
 #ifdef RINGBUFFER
@@ -657,6 +658,7 @@ void tunnelsend(u8 * buf, u16 l, tunnelidt t)
                STAT(tunnel_tx_errors);
                return;
        }
+
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        *(u32 *) & addr.sin_addr = htonl(tunnel[t].ip);
@@ -762,6 +764,10 @@ static void processipout(u8 * buf, int len)
        t = session[s].tunnel;
        sp = &session[s];
 
+       // run access-list if any
+       if (session[s].filter_out && !ip_filter(buf, len, session[s].filter_out - 1))
+               return;
+
        if (sp->tbf_out)
        {
                // Are we throttling this session?
@@ -783,11 +789,7 @@ static void processipout(u8 * buf, int len)
        // Add on L2TP header
        {
                u8 *p = makeppp(b, sizeof(b), buf, len, t, s, PPPIP);
-               if (!p)
-               {
-                       LOG(3, session[s].ip, s, t, "failed to send packet in processipout.\n");
-                       return;
-               }
+               if (!p) return;
                tunnelsend(b, len + (p-b), t); // send it...
        }
 
@@ -837,11 +839,7 @@ static void send_ipout(sessionidt s, u8 *buf, int len)
        // Add on L2TP header
        {
                u8 *p = makeppp(b, sizeof(b),  buf, len, t, s, PPPIP);
-               if (!p)
-               {
-                       LOG(3, session[s].ip, s, t, "failed to send packet in send_ipout.\n");
-                       return;
-               }
+               if (!p) return;
                tunnelsend(b, len + (p-b), t); // send it...
        }
 
@@ -949,8 +947,10 @@ static void controladd(controlt * c, tunnelidt t, sessionidt s)
                tunnel[t].controle->next = c;
        else
                tunnel[t].controls = c;
+
        tunnel[t].controle = c;
        tunnel[t].controlc++;
+
        // send now if space in window
        if (tunnel[t].controlc <= tunnel[t].window)
        {
@@ -1006,6 +1006,44 @@ void throttle_session(sessionidt s, int rate_in, int rate_out)
        }
 }
 
+// add/remove filters from session (-1 = no change)
+void filter_session(sessionidt s, int filter_in, int filter_out)
+{
+       if (!session[s].tunnel)
+               return; // No-one home.
+
+       if (!*session[s].user)
+               return; // User not logged in
+
+       // paranoia
+       if (filter_in > MAXFILTER) filter_in = -1;
+       if (filter_out > MAXFILTER) filter_out = -1;
+       if (session[s].filter_in > MAXFILTER) session[s].filter_in = 0;
+       if (session[s].filter_out > MAXFILTER) session[s].filter_out = 0;
+
+       if (filter_in >= 0)
+       {
+               if (session[s].filter_in)
+                       ip_filters[session[s].filter_in - 1].used--;
+
+               if (filter_in > 0)
+                       ip_filters[filter_in - 1].used++;
+
+               session[s].filter_in = filter_in;
+       }
+
+       if (filter_out >= 0)
+       {
+               if (session[s].filter_out)
+                       ip_filters[session[s].filter_out - 1].used--;
+
+               if (filter_out > 0)
+                       ip_filters[filter_out - 1].used++;
+
+               session[s].filter_out = filter_out;
+       }
+}
+
 // start tidy shutdown of session
 void sessionshutdown(sessionidt s, char *reason)
 {
@@ -1052,15 +1090,20 @@ void sessionshutdown(sessionidt s, char *reason)
        if (session[s].ip)
        {                          // IP allocated, clear and unroute
                int r;
+               int routed = 0;
                for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++)
                {
+                       if ((session[s].ip & session[s].route[r].mask) ==
+                           (session[s].route[r].ip & session[s].route[r].mask))
+                               routed++;
+
                        routeset(s, session[s].route[r].ip, session[s].route[r].mask, 0, 0);
                        session[s].route[r].ip = 0;
                }
 
                if (session[s].ip_pool_index == -1) // static ip
                {
-                       routeset(s, session[s].ip, 0, 0, 0);    // Delete route.
+                       if (!routed) routeset(s, session[s].ip, 0, 0, 0);
                        session[s].ip = 0;
                }
                else
@@ -1080,6 +1123,10 @@ void sessionshutdown(sessionidt s, char *reason)
        if (!session[s].die)
                session[s].die = now() + 150; // Clean up in 15 seconds
 
+       // update filter refcounts
+       if (session[s].filter_in) ip_filters[session[s].filter_in - 1].used--;
+       if (session[s].filter_out) ip_filters[session[s].filter_out - 1].used--;
+
        cluster_send_session(s);
 }
 
@@ -1109,11 +1156,7 @@ void sendipcp(tunnelidt t, sessionidt s)
        }
 
        q = makeppp(buf,sizeof(buf), 0, 0, t, s, PPPIPCP);
-       if (!q)
-       {
-               LOG(3, session[s].ip, s, t, "failed to send packet in sendipcp.\n");
-               return;
-       }
+       if (!q) return;
 
        *q = ConfigReq;
        q[1] = r << RADIUS_SHIFT;                    // ID, dont care, we only send one type of request
@@ -1312,10 +1355,9 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                        STAT(tunnel_rx_errors);
                        return;
                }
-               LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Control message (%d bytes): (unacked %d) l-ns %d l-nr %d r-ns %d r-nr %d\n",
-                               l, tunnel[t].controlc, tunnel[t].ns, tunnel[t].nr, ns, nr);
-               // if no tunnel specified, assign one
-               if (!t)
+
+               // check for duplicate tunnel open message
+               if (!t && ns == 0)
                {
                        int i;
 
@@ -1329,10 +1371,15 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                        tunnel[i].port != ntohs(addr->sin_port) )
                                        continue;
                                t = i;
+                               LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Duplicate SCCRQ?\n");
                                break;
                        }
                }
 
+               LOG(3, ntohl(addr->sin_addr.s_addr), s, t, "Control message (%d bytes): (unacked %d) l-ns %d l-nr %d r-ns %d r-nr %d\n",
+                               l, tunnel[t].controlc, tunnel[t].ns, tunnel[t].nr, ns, nr);
+
+               // if no tunnel specified, assign one
                if (!t)
                {
                        if (!(t = new_tunnel()))
@@ -1345,8 +1392,28 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                        tunnel[t].ip = ntohl(*(ipt *) & addr->sin_addr);
                        tunnel[t].port = ntohs(addr->sin_port);
                        tunnel[t].window = 4; // default window
-                       LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, "   New tunnel from %u.%u.%u.%u/%u ID %d\n", tunnel[t].ip >> 24, tunnel[t].ip >> 16 & 255, tunnel[t].ip >> 8 & 255, tunnel[t].ip & 255, tunnel[t].port, t);
                        STAT(tunnel_created);
+                       LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, "   New tunnel from %u.%u.%u.%u/%u ID %d\n",
+                               tunnel[t].ip >> 24, tunnel[t].ip >> 16 & 255,
+                               tunnel[t].ip >> 8 & 255, tunnel[t].ip & 255, tunnel[t].port, t);
+               }
+
+                       // If the 'ns' just received is not the 'nr' we're
+                       // expecting, just send an ack and drop it.
+                       //
+                       // if 'ns' is less, then we got a retransmitted packet.
+                       // if 'ns' is greater than missed a packet. Either way
+                       // we should ignore it.
+               if (ns != tunnel[t].nr)
+               {
+                       // is this the sequence we were expecting?
+                       STAT(tunnel_rx_errors);
+                       LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, "   Out of sequence tunnel %d, (%d is not the expected %d)\n",
+                               t, ns, tunnel[t].nr);
+
+                       if (l)  // Is this not a ZLB?
+                               controlnull(t);
+                       return;
                }
 
                // This is used to time out old tunnels
@@ -1356,7 +1423,7 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                {
                        int skip = tunnel[t].window; // track how many in-window packets are still in queue
                                // some to clear maybe?
-                       while (tunnel[t].controlc && (((tunnel[t].ns - tunnel[t].controlc) - nr) & 0x8000))
+                       while (tunnel[t].controlc > 0 && (((tunnel[t].ns - tunnel[t].controlc) - nr) & 0x8000))
                        {
                                controlt *c = tunnel[t].controls;
                                tunnel[t].controls = c->next;
@@ -1367,22 +1434,6 @@ void processudp(u8 * buf, int len, struct sockaddr_in *addr)
                                tunnel[t].try = 0; // we have progress
                        }
 
-                               // If the 'ns' just received is not the 'nr' we're
-                               // expecting, just send an ack and drop it.
-                               //
-                               // if 'ns' is less, then we got a retransmitted packet.
-                               // if 'ns' is greater than missed a packet. Either way
-                               // we should ignore it.
-                       if (ns != tunnel[t].nr)
-                       {
-                               // is this the sequence we were expecting?
-                               LOG(1, ntohl(addr->sin_addr.s_addr), 0, t, "   Out of sequence tunnel %d, (%d is not the expected %d)\n", t, ns, tunnel[t].nr);
-                               STAT(tunnel_rx_errors);
-
-                               if (l)  // Is this not a ZLB?
-                                       controlnull(t);
-                               return;
-                       }
                        // receiver advance (do here so quoted correctly in any sends below)
                        if (l) tunnel[t].nr = (ns + 1);
                        if (skip < 0) skip = 0;
@@ -2067,11 +2118,7 @@ static int regular_cleanups(void)
                        u8 b[MAXCONTROL] = {0};
 
                        u8 *q = makeppp(b, sizeof(b), 0, 0, session[s].tunnel, s, PPPLCP);
-                       if (!q)
-                       {
-                               LOG(3, session[s].ip, s, t, "failed to send ECHO packet.\n");
-                               continue;
-                       }
+                       if (!q) continue;
 
                        *q = EchoReq;
                        *(u8 *)(q + 1) = (time_now % 255); // ID
@@ -2130,6 +2177,22 @@ static int regular_cleanups(void)
                                send++;
                        }
 
+                       if (a & CLI_SESS_NOFILTER)
+                       {
+                               LOG(2, 0, s, session[s].tunnel, "Un-filtering session by CLI\n");
+                               filter_session(s, 0, 0);
+                               send++;
+                       }
+                       else if (a & CLI_SESS_FILTER)
+                       {
+                               LOG(2, 0, s, session[s].tunnel, "Filtering session by CLI (in=%d, out=%d)\n",
+                                   cli_session_actions[s].filter_in,
+                                   cli_session_actions[s].filter_out);
+
+                               filter_session(s, cli_session_actions[s].filter_in, cli_session_actions[s].filter_out);
+                               send++;
+                       }
+
                        if (send)
                                cluster_send_session(s);
 
@@ -2490,12 +2553,12 @@ static void initdata(int optdebug, char *optconfig)
                LOG(0, 0, 0, 0, "Error doing malloc for _statistics: %s\n", strerror(errno));
                exit(1);
        }
-       if (!(config = shared_malloc(sizeof(struct configt))))
+       if (!(config = shared_malloc(sizeof(configt))))
        {
                LOG(0, 0, 0, 0, "Error doing malloc for configuration: %s\n", strerror(errno));
                exit(1);
        }
-       memset(config, 0, sizeof(struct configt));
+       memset(config, 0, sizeof(configt));
        time(&config->start_time);
        strncpy(config->config_file, optconfig, strlen(optconfig));
        config->debug = optdebug;
@@ -2531,6 +2594,13 @@ static void initdata(int optdebug, char *optconfig)
                exit(1);
        }
 
+if (!(ip_filters = shared_malloc(sizeof(ip_filtert) * MAXFILTER)))
+{
+       LOG(0, 0, 0, 0, "Error doing malloc for ip_filters: %s\n", strerror(errno));
+       exit(1);
+}
+memset(ip_filters, 0, sizeof(ip_filtert) * MAXFILTER);
+
 #ifdef RINGBUFFER
        if (!(ringbuffer = shared_malloc(sizeof(struct Tringbuffer))))
        {
@@ -2582,11 +2652,11 @@ static void initdata(int optdebug, char *optconfig)
        _statistics->start_time = _statistics->last_reset = time(NULL);
 
 #ifdef BGP
-       if (!(bgp_peers = shared_malloc(sizeof(struct bgp_peer) * BGP_NUM_PEERS)))
-       {
-               LOG(0, 0, 0, 0, "Error doing malloc for bgp: %s\n", strerror(errno));
-               exit(1);
-       }
+       if (!(bgp_peers = shared_malloc(sizeof(struct bgp_peer) * BGP_NUM_PEERS)))
+       {
+               LOG(0, 0, 0, 0, "Error doing malloc for bgp: %s\n", strerror(errno));
+               exit(1);
+       }
 #endif /* BGP */
 }
 
@@ -3576,18 +3646,30 @@ int sessionsetup(tunnelidt t, sessionidt s)
                }
        }
 
+       {
+               int routed = 0;
+
                // Add the route for this session.
-               //
-               // Static IPs need to be routed. Anything else
-               // is part of the IP address pool and is already routed,
-               // it just needs to be added to the IP cache.
-       if (session[s].ip_pool_index == -1) // static ip
-               routeset(s, session[s].ip, 0, 0, 1);
-       else
-               cache_ipmap(session[s].ip, s);
+               for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++)
+               {
+                       if ((session[s].ip & session[s].route[r].mask) ==
+                           (session[s].route[r].ip & session[s].route[r].mask))
+                               routed++;
 
-       for (r = 0; r < MAXROUTE && session[s].route[r].ip; r++)
-               routeset(s, session[s].route[r].ip, session[s].route[r].mask, 0, 1);
+                       routeset(s, session[s].route[r].ip, session[s].route[r].mask, 0, 1);
+               }
+
+               // Static IPs need to be routed if not already
+               // convered by a Framed-Route.  Anything else is part
+               // of the IP address pool and is already routed, it
+               // just needs to be added to the IP cache.
+               if (session[s].ip_pool_index == -1) // static ip
+               {
+                       if (!routed) routeset(s, session[s].ip, 0, 0, 1);
+               }
+               else
+                       cache_ipmap(session[s].ip, s);
+       }
 
        if (!session[s].unique_id)
        {
@@ -3631,6 +3713,7 @@ int sessionsetup(tunnelidt t, sessionidt s)
 int load_session(sessionidt s, sessiont *new)
 {
        int i;
+       int newip = 0;
 
                // Sanity checks.
        if (new->ip_pool_index >= MAXIPPOOL ||
@@ -3648,46 +3731,89 @@ int load_session(sessionidt s, sessiont *new)
 
        session[s].tunnel = new->tunnel; // For logging in cache_ipmap
 
+       // See if routes/ip cache need updating
+       if (new->ip != session[s].ip)
+               newip++;
+
+       for (i = 0; !newip && i < MAXROUTE && (session[s].route[i].ip || new->route[i].ip); i++)
+               if (new->route[i].ip != session[s].route[i].ip ||
+                   new->route[i].mask != session[s].route[i].mask)
+                       newip++;
 
-       if (new->ip != session[s].ip)   // Changed ip. fix up hash tables.
+       // needs update
+       if (newip)
        {
-               if (session[s].ip)      // If there's an old one, remove it.
+               int routed = 0;
+
+               // remove old routes...
+               for (i = 0; i < MAXROUTE && session[s].route[i].ip; i++)
                {
-                       // Remove any routes if the IP has changed
-                       for (i = 0; i < MAXROUTE && session[s].route[i].ip; i++)
-                       {
-                               routeset(s, session[s].route[i].ip, session[s].route[i].mask, 0, 0);
-                               session[s].route[i].ip = 0;
-                       }
+                       if ((session[s].ip & session[s].route[i].mask) ==
+                           (session[s].route[i].ip & session[s].route[i].mask))
+                               routed++;
 
+                       routeset(s, session[s].route[i].ip, session[s].route[i].mask, 0, 0);
+               }
+
+               // ...ip
+               if (session[s].ip)
+               {
                        if (session[s].ip_pool_index == -1) // static IP
-                               routeset(s, session[s].ip, 0, 0, 0);
+                       {
+                               if (!routed) routeset(s, session[s].ip, 0, 0, 0);
+                       }
                        else            // It's part of the IP pool, remove it manually.
                                uncache_ipmap(session[s].ip);
                }
 
+               routed = 0;
+
+               // add new routes...
+               for (i = 0; i < MAXROUTE && new->route[i].ip; i++)
+               {
+                       if ((new->ip & new->route[i].mask) ==
+                           (new->route[i].ip & new->route[i].mask))
+                               routed++;
+
+                       routeset(s, new->route[i].ip, new->route[i].mask, 0, 1);
+               }
+
+               // ...ip
                if (new->ip)
                {
                        // If there's a new one, add it.
                        if (new->ip_pool_index == -1)
-                               routeset(s, new->ip, 0, 0, 1);
+                       {
+                               if (!routed) routeset(s, new->ip, 0, 0, 1);
+                       }
                        else
                                cache_ipmap(new->ip, s);
                }
        }
 
-       // Update routed networks
-       for (i = 0; i < MAXROUTE && (session[s].route[i].ip || new->route[i].ip); i++)
+       // check filters
+       if (new->filter_in && (new->filter_in > MAXFILTER || !ip_filters[new->filter_in - 1].name[0]))
        {
-               if (new->route[i].ip == session[s].route[i].ip &&
-                   new->route[i].mask == session[s].route[i].mask)
-                       continue;
+               LOG(2, session[s].ip, s, session[s].tunnel, "Dropping invalid input filter %d\n", (int) new->filter_in);
+               new->filter_in = 0;
+       }
 
-               if (session[s].route[i].ip) // Remove the old one if it exists.
-                       routeset(s, session[s].route[i].ip, session[s].route[i].mask, 0, 0);
+       if (new->filter_out && (new->filter_out > MAXFILTER || !ip_filters[new->filter_out - 1].name[0]))
+       {
+               LOG(2, session[s].ip, s, session[s].tunnel, "Dropping invalid output filter %d\n", (int) new->filter_out);
+               new->filter_out = 0;
+       }
 
-               if (new->route[i].ip)   // Add the new one if it exists.
-                       routeset(s, new->route[i].ip, new->route[i].mask, 0, 1);
+       if (new->filter_in != session[s].filter_in)
+       {
+               if (session[s].filter_in) ip_filters[session[s].filter_in - 1].used--;
+               if (new->filter_in)       ip_filters[new->filter_in - 1].used++;
+       }
+
+       if (new->filter_out != session[s].filter_out)
+       {
+               if (session[s].filter_out) ip_filters[session[s].filter_out - 1].used--;
+               if (new->filter_out)       ip_filters[new->filter_out - 1].used++;
        }
 
        if (new->tunnel && s > config->cluster_highest_sessionid)       // Maintain this in the slave. It's used
@@ -3760,10 +3886,12 @@ static int add_plugin(char *plugin_name)
                sessionbyuser,
                sessiontbysessionidt,
                sessionidtbysessiont,
-               sessionkill,
                radiusnew,
                radiussend,
                getconfig,
+               sessionkill,
+               throttle_session,
+               cluster_send_session,
        };
 
        void *p = open_plugin(plugin_name, 1);
@@ -3857,17 +3985,20 @@ static int remove_plugin(char *plugin_name)
 int run_plugins(int plugin_type, void *data)
 {
        int (*func)(void *data);
-       if (!plugins[plugin_type] || plugin_type > max_plugin_functions) return 1;
+
+       if (!plugins[plugin_type] || plugin_type > max_plugin_functions)
+               return PLUGIN_RET_ERROR;
 
        ll_reset(plugins[plugin_type]);
        while ((func = ll_next(plugins[plugin_type])))
        {
-               int rc;
-               rc = func(data);
-               if (rc == PLUGIN_RET_STOP) return 1;
-               if (rc == PLUGIN_RET_ERROR) return 0;
+               int r = func(data);
+
+               if (r != PLUGIN_RET_OK)
+                       return r; // stop here
        }
-       return 1;
+
+       return PLUGIN_RET_OK;
 }
 
 static void plugins_done()
@@ -3964,8 +4095,17 @@ static void processcontrol(u8 * buf, int len, struct sockaddr_in *addr, int alen
 
        case NSCTL_REQ_CONTROL:
                {
-                       struct param_control param = { request.argc, request.argv, 0, NULL };
-                       if (!run_plugins(PLUGIN_CONTROL, &param))
+                       struct param_control param = {
+                               config->cluster_iam_master,
+                               request.argc,
+                               request.argv,
+                               0,
+                               NULL,
+                       };
+
+                       int r = run_plugins(PLUGIN_CONTROL, &param);
+
+                       if (r == PLUGIN_RET_ERROR)
                        {
                                response.type = NSCTL_RES_ERR;
                                response.argc = 1;
@@ -3973,6 +4113,22 @@ static void processcontrol(u8 * buf, int len, struct sockaddr_in *addr, int alen
                                        ? param.additional
                                        : "error returned by plugin";
                        }
+                       else if (r == PLUGIN_RET_NOTMASTER)
+                       {
+                               static char msg[] = "must be run on master: 000.000.000.000";
+
+                               response.type = NSCTL_RES_ERR;
+                               response.argc = 1;
+                               if (config->cluster_master_address)
+                               {
+                                       strcpy(msg + 23, inet_toa(config->cluster_master_address));
+                                       response.argv[0] = msg;
+                               }
+                               else
+                               {
+                                       response.argv[0] = "must be run on master: none elected";
+                               }
+                       }
                        else if (!(param.response & NSCTL_RESPONSE))
                        {
                                response.type = NSCTL_RES_ERR;
@@ -4228,3 +4384,107 @@ static int unhide_avp(u8 *avp, tunnelidt t, sessionidt s, u16 length)
        return hidden_length + 6;
 }
 
+static int ip_filter_port(ip_filter_portt *p, portt port)
+{
+       switch (p->op)
+       {
+       case FILTER_PORT_OP_EQ:    return port == p->port;
+       case FILTER_PORT_OP_NEQ:   return port != p->port;
+       case FILTER_PORT_OP_GT:    return port > p->port;
+       case FILTER_PORT_OP_LT:    return port < p->port;
+       case FILTER_PORT_OP_RANGE: return port >= p->port && port <= p->port2;
+       }
+
+       return 0;
+}
+
+static int ip_filter_flag(u8 op, u8 sflags, u8 cflags, u8 flags)
+{
+       switch (op)
+       {
+       /*
+        * NOTE: "match-any +A +B -C -D" is interpreted as "match if
+        * either A or B is set *and* C or D is clear".  While "or" is
+        * possibly more correct, the way "established" is currently
+        * implemented depends on this behaviour.
+        */
+       case FILTER_FLAG_OP_ANY:
+               return (flags & sflags) && !(flags & cflags);
+
+       case FILTER_FLAG_OP_ALL:
+               return (flags & sflags) == sflags && (~flags & cflags) == cflags;
+       }
+
+       return 0;
+}
+
+int ip_filter(u8 *buf, int len, u8 filter)
+{
+       u8 proto;
+       ipt src_ip;
+       ipt dst_ip;
+       portt src_port = 0;
+       portt dst_port = 0;
+       u8 flags = 0;
+       ip_filter_rulet *rule;
+
+       if (len < 20) // up to end of destination address
+               return 0;
+
+       if (*buf >> 4) // IPv4
+               return 0;
+
+       proto = buf[9];
+       src_ip = *(u32 *) (buf + 12);
+       dst_ip = *(u32 *) (buf + 16);
+
+       if (proto == IPPROTO_TCP || proto == IPPROTO_UDP)
+       {
+               int l = buf[0] & 0xf;
+               if (len < l + 4) // ports
+                       return 0;
+
+               src_port = ntohs(*(u16 *) (buf + l));
+               dst_port = ntohs(*(u16 *) (buf + l + 2));
+               if (proto == IPPROTO_TCP)
+               {
+                       if (len < l + 15) // flags
+                               return 0;
+
+                       flags = buf[l + 14] & 0x3f;
+               }
+       }
+
+       for (rule = ip_filters[filter].rules; rule->action; rule++)
+       {
+               if (proto && proto != rule->proto)
+                       continue;
+
+               if (rule->src_wild != INADDR_BROADCAST &&
+                   (src_ip & ~rule->src_wild) != (rule->src_ip & ~rule->src_wild))
+                       continue;
+
+               if (rule->dst_wild != INADDR_BROADCAST &&
+                   (dst_ip & ~rule->dst_wild) != (rule->dst_ip & ~rule->dst_wild))
+                       continue;
+
+               if (proto == IPPROTO_TCP || proto == IPPROTO_UDP)
+               {
+                       if (rule->src_ports.op && !ip_filter_port(&rule->src_ports, src_port))
+                               continue;
+
+                       if (rule->dst_ports.op && !ip_filter_port(&rule->dst_ports, dst_port))
+                               continue;
+
+                       if (proto == IPPROTO_TCP && rule->tcp_flag_op &&
+                           !ip_filter_flag(rule->tcp_flag_op, rule->tcp_sflags, rule->tcp_cflags, flags))
+                               continue;
+               }
+
+               // matched
+               return rule->action == FILTER_ACTION_PERMIT;
+       }
+
+       // default deny
+       return 0;
+}