implement an arp filter
authorhenning <henning@openbsd.org>
Mon, 5 Feb 2018 03:51:53 +0000 (03:51 +0000)
committerhenning <henning@openbsd.org>
Mon, 5 Feb 2018 03:51:53 +0000 (03:51 +0000)
allows arp (and rarp) requests and replies to be matched, including matching
based on the source and target host and protocol adresses, and thus control
over arp traffic and learning.
written for medical x-ray machines, but useful in many spread out L2 networks
ok claudio benno

sbin/ifconfig/brconfig.c
sbin/ifconfig/ifconfig.8
sys/net/bridgectl.c
sys/net/if_bridge.c
sys/net/if_bridge.h

index 1f240b1..1e3415b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: brconfig.c,v 1.16 2017/07/31 02:32:11 jsg Exp $       */
+/*     $OpenBSD: brconfig.c,v 1.17 2018/02/05 03:51:53 henning Exp $   */
 
 /*
  * Copyright (c) 1999, 2000 Jason L. Wright (jason@thought.net)
@@ -46,6 +46,7 @@
 #include <errno.h>
 #include <getopt.h>
 #include <limits.h>
+#include <arpa/inet.h>
 
 #include "brconfig.h"
 
@@ -57,6 +58,7 @@ void bridge_cfg(const char *);
 void bridge_badrule(int, char **, int);
 void bridge_showrule(struct ifbrlreq *);
 int is_switch(char *);
+int bridge_arprule(struct ifbrlreq *, int *, char ***);
 
 #define        IFBAFBITS       "\020\1STATIC"
 #define        IFBIFBITS       \
@@ -821,6 +823,25 @@ bridge_showrule(struct ifbrlreq *r)
        if (r->ifbr_tagname[0])
                printf(" tag %s", r->ifbr_tagname);
 
+       if (r->ifbr_arpf.brla_flags & BRLA_ARP)
+               printf(" arp");
+       if (r->ifbr_arpf.brla_flags & BRLA_RARP)
+               printf(" rarp");
+       if (r->ifbr_arpf.brla_op == ARPOP_REQUEST ||
+           r->ifbr_arpf.brla_op == ARPOP_REVREQUEST)
+               printf(" request");
+       if (r->ifbr_arpf.brla_op == ARPOP_REPLY ||
+           r->ifbr_arpf.brla_op == ARPOP_REVREPLY)
+               printf(" reply");
+       if (r->ifbr_arpf.brla_flags & BRLA_SHA)
+               printf(" sha %s", ether_ntoa(&r->ifbr_arpf.brla_sha));
+       if (r->ifbr_arpf.brla_flags & BRLA_THA)
+               printf(" tha %s", ether_ntoa(&r->ifbr_arpf.brla_tha));
+       if (r->ifbr_arpf.brla_flags & BRLA_SPA)
+               printf(" spa %s", inet_ntoa(r->ifbr_arpf.brla_spa));
+       if (r->ifbr_arpf.brla_flags & BRLA_TPA)
+               printf(" tpa %s", inet_ntoa(r->ifbr_arpf.brla_tpa));
+
        printf("\n");
 }
 
@@ -837,14 +858,13 @@ bridge_rule(int targc, char **targv, int ln)
        int argc = targc;
        struct ifbrlreq rule;
        struct ether_addr *ea, *dea;
+       struct in_addr *ia;
 
        if (argc == 0) {
                warnx("invalid rule");
                return (1);
        }
-       rule.ifbr_tagname[0] = 0;
-       rule.ifbr_flags = 0;
-       rule.ifbr_action = 0;
+       bzero(&rule, sizeof(rule));
        strlcpy(rule.ifbr_name, name, sizeof(rule.ifbr_name));
 
        if (strcmp(argv[0], "block") == 0)
@@ -882,16 +902,19 @@ bridge_rule(int targc, char **targv, int ln)
        argc--; argv++;
 
        while (argc) {
+               dea = NULL;
                if (strcmp(argv[0], "dst") == 0) {
                        if (rule.ifbr_flags & BRL_FLAG_DSTVALID)
                                goto bad_rule;
                        rule.ifbr_flags |= BRL_FLAG_DSTVALID;
                        dea = &rule.ifbr_dst;
+                       argc--; argv++;
                } else if (strcmp(argv[0], "src") == 0) {
                        if (rule.ifbr_flags & BRL_FLAG_SRCVALID)
                                goto bad_rule;
                        rule.ifbr_flags |= BRL_FLAG_SRCVALID;
                        dea = &rule.ifbr_src;
+                       argc--; argv++;
                } else if (strcmp(argv[0], "tag") == 0) {
                        if (argc < 2) {
                                warnx("missing tag name");
@@ -901,28 +924,37 @@ bridge_rule(int targc, char **targv, int ln)
                                warnx("tag already defined");
                                goto bad_rule;
                        }
-                       if (strlcpy(rule.ifbr_tagname, argv[1],
+                       argc--; argv++;
+                       if (strlcpy(rule.ifbr_tagname, argv[0],
                            PF_TAG_NAME_SIZE) > PF_TAG_NAME_SIZE) {
-                               warnx("tag name '%s' too long", argv[1]);
+                               warnx("tag name '%s' too long", argv[0]);
                                goto bad_rule;
                        }
-                       dea = NULL;
+                       argc--; argv++;
+               } else if (strcmp(argv[0], "arp") == 0) {
+                       rule.ifbr_arpf.brla_flags |= BRLA_ARP;
+                       argc--; argv++;
+                       if (bridge_arprule(&rule, &argc, &argv) == -1)
+                               goto bad_rule;
+               } else if (strcmp(argv[0], "rarp") == 0) {
+                       rule.ifbr_arpf.brla_flags |= BRLA_RARP;
+                       argc--; argv++;
+                       if (bridge_arprule(&rule, &argc, &argv) == -1)
+                               goto bad_rule;
                } else
                        goto bad_rule;
 
-               argc--; argv++;
-
-               if (argc == 0)
-                       goto bad_rule;
                if (dea != NULL) {
+                       if (argc == 0)
+                               goto bad_rule;
                        ea = ether_aton(argv[0]);
                        if (ea == NULL) {
                                warnx("invalid address: %s", argv[0]);
                                return (1);
                        }
                        bcopy(ea, dea, sizeof(*dea));
+                       argc--; argv++;
                }
-               argc--; argv++;
        }
 
        if (ioctl(s, SIOCBRDGARL, &rule) < 0) {
@@ -936,7 +968,71 @@ bad_rule:
        return (1);
 }
 
-#define MAXRULEWORDS 8
+int
+bridge_arprule(struct ifbrlreq *rule, int *argc, char ***argv)
+{
+       while (*argc) {
+               struct ether_addr       *ea, *dea = NULL;
+               struct in_addr           ia, *dia = NULL;
+
+               if (strcmp((*argv)[0], "request") == 0) {
+                       if (rule->ifbr_arpf.brla_flags & BRLA_ARP)
+                               rule->ifbr_arpf.brla_op = ARPOP_REQUEST;
+                       else if (rule->ifbr_arpf.brla_flags & BRLA_RARP)
+                               rule->ifbr_arpf.brla_op = ARPOP_REVREQUEST;
+                       else
+                               errx(1, "bridge_arprule: arp/rarp undefined");
+               } else if (strcmp((*argv)[0], "reply") == 0) {
+                       if (rule->ifbr_arpf.brla_flags & BRLA_ARP)
+                               rule->ifbr_arpf.brla_op = ARPOP_REPLY;
+                       else if (rule->ifbr_arpf.brla_flags & BRLA_RARP)
+                               rule->ifbr_arpf.brla_op = ARPOP_REVREPLY;
+                       else
+                               errx(1, "bridge_arprule: arp/rarp undefined");
+               } else if (strcmp((*argv)[0], "sha") == 0) {
+                       rule->ifbr_arpf.brla_flags |= BRLA_SHA;
+                       dea = &rule->ifbr_arpf.brla_sha;
+               } else if (strcmp((*argv)[0], "tha") == 0) {
+                       rule->ifbr_arpf.brla_flags |= BRLA_THA;
+                       dea = &rule->ifbr_arpf.brla_tha;
+               } else if (strcmp((*argv)[0], "spa") == 0) {
+                       rule->ifbr_arpf.brla_flags |= BRLA_SPA;
+                       dia = &rule->ifbr_arpf.brla_spa;
+               } else if (strcmp((*argv)[0], "tpa") == 0) {
+                       rule->ifbr_arpf.brla_flags |= BRLA_TPA;
+                       dia = &rule->ifbr_arpf.brla_tpa;
+               } else
+                       return (0);
+
+               (*argc)--; (*argv)++;
+               if (dea != NULL) {
+                       if (*argc == 0)
+                               return (-1);
+                       ea = ether_aton((*argv)[0]);
+                       if (ea == NULL) {
+                               warnx("invalid address: %s", (*argv)[0]);
+                               return (-1);
+                       }
+                       bcopy(ea, dea, sizeof(*dea));
+                       (*argc)--; (*argv)++;
+               }
+               if (dia != NULL) {
+                       if (*argc == 0)
+                               return (-1);
+                       ia.s_addr = inet_addr((*argv)[0]);
+                       if (ia.s_addr == INADDR_NONE) {
+                               warnx("invalid address: %s", (*argv)[0]);
+                               return (-1);
+                       }
+                       bcopy(&ia, dia, sizeof(*dia));
+                       (*argc)--; (*argv)++;
+               }
+       }
+       return (0);
+}
+
+
+#define MAXRULEWORDS 32
 
 void
 bridge_rulefile(const char *fname, int d)
index ef9592c..ba2290f 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: ifconfig.8,v 1.292 2018/01/16 10:33:55 mpi Exp $
+.\"    $OpenBSD: ifconfig.8,v 1.293 2018/02/05 03:51:53 henning Exp $
 .\"    $NetBSD: ifconfig.8,v 1.11 1996/01/04 21:27:29 pk Exp $
 .\"     $FreeBSD: ifconfig.8,v 1.16 1998/02/01 07:03:29 steve Exp $
 .\"
@@ -31,7 +31,7 @@
 .\"
 .\"     @(#)ifconfig.8 8.4 (Berkeley) 6/1/94
 .\"
-.Dd $Mdocdate: January 16 2018 $
+.Dd $Mdocdate: February 5 2018 $
 .Dt IFCONFIG 8
 .Os
 .Sh NAME
@@ -689,9 +689,10 @@ like a hub or a wireless network.
 .Cm block Ns | Ns Cm pass
 .Op Cm in | out
 .Cm on Ar interface
-.Op Cm src Ar address
-.Op Cm dst Ar address
+.Op Cm src Ar lladdr
+.Op Cm dst Ar lladdr
 .Op Cm tag Ar tagname
+.Op Cm arp | rarp Ar [ request | reply ] [ Cm sha Ar lladdr ] [ Cm spa Ar ipaddr ] [ Cm tha Ar lladdr ] [ Cm tpa Ar ipaddr ]
 .Xc
 Add a filtering rule to an interface.
 Rules have a similar syntax to those in
@@ -701,6 +702,25 @@ MAC addresses.
 They can also tag packets for
 .Xr pf 4
 to filter on.
+.Xr arp 4
+packets can be matched with the
+.Cm arp
+keyword for regular and
+.Cm rarp
+for reverse arp packets.
+.Ar request
+and
+.Ar reply
+limit matches to requests or replies.
+The source and target host addresses can be matched with the
+.Cm sha
+and
+.Cm tha
+keywords,
+the protocol addresses with
+.Cm spa
+and
+.Cm tpa .
 Rules are processed in the order in which they were added
 to the interface, and the first rule matched takes the action (block or pass)
 and, if given, the tag of the rule.
index 8694916..5e431fe 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: bridgectl.c,v 1.6 2017/05/04 15:00:24 bluhm Exp $     */
+/*     $OpenBSD: bridgectl.c,v 1.7 2018/02/05 03:51:53 henning Exp $   */
 
 /*
  * Copyright (c) 1999, 2000 Jason L. Wright (jason@thought.net)
@@ -573,6 +573,7 @@ bridge_brlconf(struct bridge_softc *sc, struct ifbrlconf *bc)
                req.ifbr_flags = n->brl_flags;
                req.ifbr_src = n->brl_src;
                req.ifbr_dst = n->brl_dst;
+               req.ifbr_arpf = n->brl_arpf;
 #if NPF > 0
                req.ifbr_tagname[0] = '\0';
                if (n->brl_tag)
@@ -596,6 +597,7 @@ bridge_brlconf(struct bridge_softc *sc, struct ifbrlconf *bc)
                req.ifbr_flags = n->brl_flags;
                req.ifbr_src = n->brl_src;
                req.ifbr_dst = n->brl_dst;
+               req.ifbr_arpf = n->brl_arpf;
 #if NPF > 0
                req.ifbr_tagname[0] = '\0';
                if (n->brl_tag)
@@ -614,6 +616,51 @@ done:
        return (error);
 }
 
+u_int8_t
+bridge_arpfilter(struct brl_node *n, struct ether_header *eh, struct mbuf *m)
+{
+       struct ether_arp         ea;
+
+       if (!(n->brl_arpf.brla_flags & (BRLA_ARP|BRLA_RARP)))
+               return (1);
+
+       if (ntohs(eh->ether_type) != ETHERTYPE_ARP)
+               return (0);
+       if (m->m_pkthdr.len <= ETHER_HDR_LEN + sizeof(ea))
+               return (0);     /* log error? */
+       m_copydata(m, ETHER_HDR_LEN, sizeof(ea), (caddr_t)&ea);
+
+       if (ntohs(ea.arp_hrd) != ARPHRD_ETHER ||
+           ntohs(ea.arp_pro) != ETHERTYPE_IP ||
+           ea.arp_hln != ETHER_ADDR_LEN ||
+           ea.arp_pln != sizeof(struct in_addr))
+               return (0);
+       if ((n->brl_arpf.brla_flags & BRLA_ARP) &&
+           ntohs(ea.arp_op) != ARPOP_REQUEST &&
+           ntohs(ea.arp_op) != ARPOP_REPLY)
+               return (0);
+       if ((n->brl_arpf.brla_flags & BRLA_RARP) &&
+           ntohs(ea.arp_op) != ARPOP_REVREQUEST &&
+           ntohs(ea.arp_op) != ARPOP_REVREPLY)
+               return (0);
+       if (n->brl_arpf.brla_op && ntohs(ea.arp_op) != n->brl_arpf.brla_op)
+               return (0);
+       if (n->brl_arpf.brla_flags & BRLA_SHA &&
+           bcmp(ea.arp_sha, &n->brl_arpf.brla_sha, ETHER_ADDR_LEN))
+               return (0);
+       if (n->brl_arpf.brla_flags & BRLA_THA &&
+           bcmp(ea.arp_tha, &n->brl_arpf.brla_tha, ETHER_ADDR_LEN))
+               return (0);
+       if (n->brl_arpf.brla_flags & BRLA_SPA &&
+           bcmp(ea.arp_spa, &n->brl_arpf.brla_spa, sizeof(struct in_addr)))
+               return (0);
+       if (n->brl_arpf.brla_flags & BRLA_TPA &&
+           bcmp(ea.arp_tpa, &n->brl_arpf.brla_tpa, sizeof(struct in_addr)))
+               return (0);
+
+       return (1);
+}
+
 u_int8_t
 bridge_filterrule(struct brl_head *h, struct ether_header *eh, struct mbuf *m)
 {
@@ -621,6 +668,8 @@ bridge_filterrule(struct brl_head *h, struct ether_header *eh, struct mbuf *m)
        u_int8_t flags;
 
        SIMPLEQ_FOREACH(n, h, brl_next) {
+               if (!bridge_arpfilter(n, eh, m))
+                       continue;
                flags = n->brl_flags & (BRL_FLAG_SRCVALID|BRL_FLAG_DSTVALID);
                if (flags == 0)
                        goto return_action;
@@ -663,6 +712,7 @@ bridge_addrule(struct bridge_iflist *bif, struct ifbrlreq *req, int out)
        bcopy(&req->ifbr_dst, &n->brl_dst, sizeof(struct ether_addr));
        n->brl_action = req->ifbr_action;
        n->brl_flags = req->ifbr_flags;
+       n->brl_arpf = req->ifbr_arpf;
 #if NPF > 0
        if (req->ifbr_tagname[0])
                n->brl_tag = pf_tagname2tag(req->ifbr_tagname, 1);
index fa37dfd..a54f5ba 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_bridge.c,v 1.301 2018/01/10 23:50:39 dlg Exp $     */
+/*     $OpenBSD: if_bridge.c,v 1.302 2018/02/05 03:51:53 henning Exp $ */
 
 /*
  * Copyright (c) 1999, 2000 Jason L. Wright (jason@thought.net)
@@ -699,6 +699,7 @@ bridge_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa,
        struct ether_addr *dst;
        struct bridge_softc *sc;
        struct bridge_tunneltag *brtag;
+       struct bridge_iflist *ifl;
        int error;
 
        /* ifp must be a member interface of the bridge. */
@@ -785,6 +786,12 @@ bridge_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *sa,
                                }
                        }
 
+                       ifl = (struct bridge_iflist *)dst_if->if_bridgeport;
+                       KASSERT(ifl != NULL);
+                       if (bridge_filterrule(&ifl->bif_brlout, eh, mc) ==
+                           BRL_ACTION_BLOCK)
+                               continue;
+
                        error = bridge_ifenqueue(sc, dst_if, mc);
                        if (error)
                                continue;
index 55a3fa7..75596a5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_bridge.h,v 1.55 2017/01/20 05:03:48 claudio Exp $  */
+/*     $OpenBSD: if_bridge.h,v 1.56 2018/02/05 03:51:53 henning Exp $  */
 
 /*
  * Copyright (c) 1999, 2000 Jason L. Wright (jason@thought.net)
@@ -196,6 +196,21 @@ struct ifbropreq {
 /*
  * Bridge mac rules
  */
+struct ifbrarpf {
+       u_int16_t               brla_flags;
+       u_int16_t               brla_op;
+       struct ether_addr       brla_sha;
+       struct in_addr          brla_spa;
+       struct ether_addr       brla_tha;
+       struct in_addr          brla_tpa;
+};
+#define        BRLA_ARP        0x01
+#define        BRLA_RARP       0x02
+#define        BRLA_SHA        0x10
+#define        BRLA_SPA        0x20
+#define        BRLA_THA        0x40
+#define        BRLA_TPA        0x80
+
 struct ifbrlreq {
        char                    ifbr_name[IFNAMSIZ];    /* bridge ifs name */
        char                    ifbr_ifsname[IFNAMSIZ]; /* member ifs name */
@@ -204,6 +219,7 @@ struct ifbrlreq {
        struct ether_addr       ifbr_src;               /* source mac */
        struct ether_addr       ifbr_dst;               /* destination mac */
        char                    ifbr_tagname[PF_TAG_NAME_SIZE]; /* pf tagname */
+       struct ifbrarpf         ifbr_arpf;              /* arp filter */
 };
 #define        BRL_ACTION_BLOCK        0x01                    /* block frame */
 #define        BRL_ACTION_PASS         0x02                    /* pass frame */
@@ -269,6 +285,7 @@ struct brl_node {
        u_int16_t               brl_tag;        /* pf tag ID */
        u_int8_t                brl_action;     /* what to do with match */
        u_int8_t                brl_flags;      /* comparision flags */
+       struct ifbrarpf         brl_arpf;       /* arp filter */
 };
 
 struct bstp_timer {