On Fri, Dec 09, 2016 at 11:55:17PM +0100, Reyk Floeter wrote:
> On Fri, Dec 09, 2016 at 10:08:09AM +0100, Rafael Zalamena wrote:
> > On Thu, Dec 08, 2016 at 08:43:20PM +0100, Rafael Zalamena wrote:
> > > This diff implements layer 2 relaying support for dhcrelay with further
> > > support for Relay Agent Info (RFC 3046). This feature is mostly used by
> > > switched networks that might not be using IP addresses when in the edge
> > > with the customer.
> > > 
> > > Basically this diff allows you to run dhcrelay on interfaces without
> > > addresses and doesn't require you to specify an DHCP server address.
> > > Instead you just need to specify the output port.
> > > 
> > > I also updated the man page to show the new options for layer 2 relaying
> > > Relay Agent Info knobs, since you might want to let the remote DHCP
> > > server know where the DHCP packet is coming from.
> > 
> > I forgot to add the man page in the last diff, here is a new one with
> > the man page modifications.
> > 
> > ok?
> > 
> 
> See comments below.
> 

Thanks for the in-depth review, however see comments for the following
snippets:

> For the circuit-id, you could default to the interface name or index
> where the packet was received on.  The remote-id could even default to
> the an hostname or address.  And how does it differ from the existing
> -o (see below)?

Yes we could do that, but then we also have to define a way to make dhcrelay
not use Relay Agent Information (L2 without packet modifications). Can we
fix this in another diff?

> The encoding is different to the DHO_RELAY_AGENT_INFORMATION (option
> 82) that I added for enc0/IPsec where the circuit-id is just the
> interface index and the remote id an IP address.  I know this is
> related to the other standard, but could this be merged with
> relay_agentinfo() somehow or documented that there is a difference?

It is actually the same standard you just added different information,
normally you can use the 'giaddr' for layer 3 identification, but I
guess you stumbled on something else and you added relay_agentinfo().

I'm happy to make Layer 3 Relay Agent Information use the same code, but
can we do that in another diff?

Beside those two points mentioned above, I fixed everything else you
commented.

ok?

Index: bpf.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/bpf.c,v
retrieving revision 1.13
diff -u -p -r1.13 bpf.c
--- bpf.c       8 Dec 2016 19:18:15 -0000       1.13
+++ bpf.c       10 Dec 2016 01:52:46 -0000
@@ -93,6 +93,38 @@ if_register_send(struct interface_info *
 }
 
 /*
+ * Packet filter program: 'ip and udp and dst port CLIENT_PORT'
+ */
+struct bpf_insn dhcp_bpf_sfilter[] = {
+       /* Make sure this is an IP packet... */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+       /* Make sure it's a UDP packet... */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+       /* Make sure this isn't a fragment... */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
+       BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+       /* Get the IP header length... */
+       BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+       /* Make sure it's to the right port... */
+       BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, CLIENT_PORT, 0, 1),
+
+       /* If we passed all the tests, ask for the whole packet. */
+       BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+
+       /* Otherwise, drop it. */
+       BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+int dhcp_bpf_sfilter_len = sizeof(dhcp_bpf_sfilter) / sizeof(struct bpf_insn);
+
+/*
  * Packet filter program: 'ip and udp and dst port SERVER_PORT'
  */
 struct bpf_insn dhcp_bpf_filter[] = {
@@ -161,6 +193,38 @@ struct bpf_insn dhcp_bpf_efilter[] = {
 int dhcp_bpf_efilter_len = sizeof(dhcp_bpf_efilter) / sizeof(struct bpf_insn);
 
 /*
+ * Packet write filter program: 'ip and udp and src port CLIENT_PORT'
+ */
+struct bpf_insn dhcp_bpf_swfilter[] = {
+       /* Make sure this is an IP packet... */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+       /* Make sure it's a UDP packet... */
+       BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+       /* Make sure this isn't a fragment... */
+       BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20),
+       BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+       /* Get the IP header length... */
+       BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14),
+
+       /* Make sure it's from the right port... */
+       BPF_STMT(BPF_LD + BPF_H + BPF_IND, 14),
+       BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, CLIENT_PORT, 0, 1),
+
+       /* If we passed all the tests, ask for the whole packet. */
+       BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
+
+       /* Otherwise, drop it. */
+       BPF_STMT(BPF_RET+BPF_K, 0),
+};
+
+int dhcp_bpf_swfilter_len = sizeof(dhcp_bpf_swfilter) / sizeof(struct 
bpf_insn);
+
+/*
  * Packet write filter program: 'ip and udp and src port SERVER_PORT'
  */
 struct bpf_insn dhcp_bpf_wfilter[] = {
@@ -193,7 +257,7 @@ struct bpf_insn dhcp_bpf_wfilter[] = {
 int dhcp_bpf_wfilter_len = sizeof(dhcp_bpf_wfilter) / sizeof(struct bpf_insn);
 
 void
-if_register_receive(struct interface_info *info)
+if_register_receive(struct interface_info *info, int isserver)
 {
        struct bpf_version v;
        struct bpf_program p;
@@ -234,7 +298,10 @@ if_register_receive(struct interface_inf
        info->rbuf_len = 0;
 
        /* Set up the bpf filter program structure. */
-       if (info->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
+       if (isserver) {
+               p.bf_len = dhcp_bpf_sfilter_len;
+               p.bf_insns = dhcp_bpf_sfilter;
+       } else if (info->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
                p.bf_len = dhcp_bpf_efilter_len;
                p.bf_insns = dhcp_bpf_efilter;
        } else {
@@ -245,8 +312,13 @@ if_register_receive(struct interface_inf
                error("Can't install packet filter program: %m");
 
        /* Set up the bpf write filter program structure. */
-       p.bf_len = dhcp_bpf_wfilter_len;
-       p.bf_insns = dhcp_bpf_wfilter;
+       if (isserver) {
+               p.bf_len = dhcp_bpf_swfilter_len;
+               p.bf_insns = dhcp_bpf_swfilter;
+       } else {
+               p.bf_len = dhcp_bpf_wfilter_len;
+               p.bf_insns = dhcp_bpf_wfilter;
+       }
 
        if (ioctl(info->rfdesc, BIOCSETWF, &p) == -1)
                error("Can't install write filter program: %m");
Index: dhcpd.h
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/dhcpd.h,v
retrieving revision 1.17
diff -u -p -r1.17 dhcpd.h
--- dhcpd.h     8 Dec 2016 19:18:15 -0000       1.17
+++ dhcpd.h     10 Dec 2016 01:54:23 -0000
@@ -77,6 +77,12 @@ enum dhcp_state {
        S_REBINDING
 };
 
+/* DHCP relaying modes. */
+enum dhcp_relay_mode {
+       DRM_UNKNOWN,
+       DRM_LAYER2,
+       DRM_LAYER3,
+};
 
 struct interface_info {
        struct interface_info   *next;
@@ -123,7 +129,7 @@ int debug(char *, ...) __attribute__ ((_
 /* bpf.c */
 int if_register_bpf(struct interface_info *);
 void if_register_send(struct interface_info *);
-void if_register_receive(struct interface_info *);
+void if_register_receive(struct interface_info *, int);
 ssize_t send_packet(struct interface_info *,
     struct dhcp_packet *, size_t, struct packet_ctx *);
 ssize_t receive_packet(struct interface_info *, unsigned char *, size_t,
@@ -133,7 +139,7 @@ ssize_t receive_packet(struct interface_
 extern void (*bootp_packet_handler)(struct interface_info *,
     struct dhcp_packet *, int, struct packet_ctx *);
 struct interface_info *get_interface(const char *,
-    void (*)(struct protocol *));
+    void (*)(struct protocol *), int isserver);
 void dispatch(void);
 void got_one(struct protocol *);
 void add_protocol(char *, int, void (*)(struct protocol *), void *);
Index: dhcrelay.8
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/dhcrelay.8,v
retrieving revision 1.12
diff -u -p -r1.12 dhcrelay.8
--- dhcrelay.8  16 Jul 2013 11:13:33 -0000      1.12
+++ dhcrelay.8  10 Dec 2016 02:12:53 -0000
@@ -45,8 +45,10 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl do
+.Op Fl C Ar circuit-id
+.Op Fl R Ar remote-id
 .Fl i Ar interface
-.Ar server1 Op Ar ... serverN
+.Ar destination1 Op Ar ... destinationN
 .Sh DESCRIPTION
 The
 .Nm
@@ -58,10 +60,15 @@ other subnets.
 listens for DHCP requests on a given interface.
 When a query is received,
 .Nm
-forwards it to the list of DHCP servers specified on the command line.
+forwards it to the list of DHCP destinations specified on the command line.
 When a reply is received, it is broadcast or unicast on the network from
 whence the original request came.
 .Pp
+The server might be a name, address or interface.
+.Nm
+will operate in layer 2 mode when the specified servers are interfaces,
+otherwise it will operate in layer 3 mode.
+.Pp
 The name of at least one DHCP server to which DHCP and BOOTP requests
 should be relayed,
 as well as the name of the network interface that
@@ -73,12 +80,16 @@ must be specified on the command line.
 supports relaying of DHCP traffic to configure IPsec tunnel mode
 clients when listening on the
 .Xr enc 4
-interface.
+interface using layer 3 mode only.
 The DHCP server has to support RFC 3046 to echo back the relay agent
 information to allow stateless DHCP reply to IPsec tunnel mapping.
 .Pp
 The options are as follows:
 .Bl -tag -width Ds
+.It Fl C Ar circuit-id
+The circuit-id Relay Agent Information sub-option value that
+.Nm
+should append on layer 2 relayed packets.
 .It Fl d
 .Nm
 normally runs in the foreground until it has configured
@@ -96,6 +107,10 @@ Add the relay agent information option.
 By default, this is only enabled for the
 .Xr enc 4
 interface.
+.It Fl R Ar remote-id
+The remote-id Relay Agent Information sub-option value that
+.Nm
+should append on layer 2 relayed packets.
 .El
 .Sh SEE ALSO
 .Xr dhclient 8 ,
Index: dhcrelay.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/dhcrelay.c,v
retrieving revision 1.49
diff -u -p -r1.49 dhcrelay.c
--- dhcrelay.c  8 Dec 2016 19:18:15 -0000       1.49
+++ dhcrelay.c  11 Dec 2016 00:18:42 -0000
@@ -66,6 +66,8 @@ void   usage(void);
 int     rdaemon(int);
 void    relay(struct interface_info *, struct dhcp_packet *, int,
            struct packet_ctx *);
+void    l2relay(struct interface_info *, struct dhcp_packet *, int,
+           struct packet_ctx *);
 char   *print_hw_addr(int, int, unsigned char *);
 void    got_response(struct protocol *);
 int     get_rdomain(char *);
@@ -84,7 +86,12 @@ struct interface_info *interfaces = NULL
 int server_fd;
 int oflag;
 
+enum dhcp_relay_mode    drm = DRM_UNKNOWN;
+const char             *rai_circuit = NULL;
+const char             *rai_remote = NULL;
+
 struct server_list {
+       struct interface_info *intf;
        struct server_list *next;
        struct sockaddr_in to;
        int fd;
@@ -98,6 +105,7 @@ main(int argc, char *argv[])
        struct server_list      *sp = NULL;
        struct passwd           *pw;
        struct sockaddr_in       laddr;
+       int                      optslen;
 
        daemonize = 1;
 
@@ -105,8 +113,11 @@ main(int argc, char *argv[])
        openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY);
        setlogmask(LOG_UPTO(LOG_INFO));
 
-       while ((ch = getopt(argc, argv, "adi:o")) != -1) {
+       while ((ch = getopt(argc, argv, "aC:di:oR:")) != -1) {
                switch (ch) {
+               case 'C':
+                       rai_circuit = optarg;
+                       break;
                case 'd':
                        daemonize = 0;
                        break;
@@ -114,7 +125,7 @@ main(int argc, char *argv[])
                        if (interfaces != NULL)
                                usage();
 
-                       interfaces = get_interface(optarg, got_one);
+                       interfaces = get_interface(optarg, got_one, 0);
                        if (interfaces == NULL)
                                error("interface '%s' not found", optarg);
                        break;
@@ -122,6 +133,9 @@ main(int argc, char *argv[])
                        /* add the relay agent information option */
                        oflag++;
                        break;
+               case 'R':
+                       rai_remote = optarg;
+                       break;
 
                default:
                        usage();
@@ -135,10 +149,42 @@ main(int argc, char *argv[])
        if (argc < 1)
                usage();
 
+       if (rai_remote != NULL && rai_circuit == NULL)
+               error("you must specify a circuit-id with a remote-id");
+
+       /* Validate that we have space for all suboptions. */
+       if (rai_circuit != NULL) {
+               optslen = 2 + strlen(rai_circuit);
+               if (rai_remote != NULL)
+                       optslen += 2 + strlen(rai_remote);
+
+               if (optslen > 255)
+                       error("relay agent information is too long");
+       }
+
        while (argc > 0) {
                struct hostent          *he;
                struct in_addr           ia, *iap = NULL;
 
+               if ((sp = calloc(1, sizeof(*sp))) == NULL)
+                       error("calloc");
+
+               if ((sp->intf = get_interface(argv[0], got_one, 1)) != NULL) {
+                       if (drm == DRM_LAYER3)
+                               error("don't mix interfaces with hosts");
+
+                       if (sp->intf->hw_address.htype == HTYPE_IPSEC_TUNNEL)
+                               error("can't use IPSec with layer 2");
+
+                       sp->next = servers;
+                       servers = sp;
+
+                       drm = DRM_LAYER2;
+                       argc--;
+                       argv++;
+                       continue;
+               }
+
                if (inet_aton(argv[0], &ia))
                        iap = &ia;
                else {
@@ -149,12 +195,16 @@ main(int argc, char *argv[])
                                iap = ((struct in_addr *)he->h_addr_list[0]);
                }
                if (iap) {
-                       if ((sp = calloc(1, sizeof *sp)) == NULL)
-                               error("calloc");
+                       if (drm == DRM_LAYER2)
+                               error("don't mix interfaces with hosts");
+
+                       drm = DRM_LAYER3;
                        sp->next = servers;
                        servers = sp;
                        memcpy(&sp->to.sin_addr, iap, sizeof *iap);
-               }
+               } else
+                       free(sp);
+
                argc--;
                argv++;
        }
@@ -167,7 +217,9 @@ main(int argc, char *argv[])
 
        if (interfaces == NULL)
                error("no interface given");
-       if (interfaces->primary_address.s_addr == 0)
+       /* We need an address for running layer 3 mode. */
+       if (drm == DRM_LAYER3 &&
+           interfaces->primary_address.s_addr == 0)
                error("interface '%s' does not have an address",
                    interfaces->name);
 
@@ -192,6 +244,9 @@ main(int argc, char *argv[])
        laddr.sin_addr.s_addr = interfaces->primary_address.s_addr;
        /* Set up the server sockaddrs. */
        for (sp = servers; sp; sp = sp->next) {
+               if (sp->intf != NULL)
+                       break;
+
                sp->to.sin_port = server_port;
                sp->to.sin_family = AF_INET;
                sp->to.sin_len = sizeof sp->to;
@@ -234,7 +289,10 @@ main(int argc, char *argv[])
        tzset();
 
        time(&cur_time);
-       bootp_packet_handler = relay;
+       if (drm == DRM_LAYER3)
+               bootp_packet_handler = relay;
+       else
+               bootp_packet_handler = l2relay;
 
        if ((pw = getpwnam("_dhcp")) == NULL)
                error("user \"_dhcp\" not found");
@@ -374,7 +432,8 @@ usage(void)
 {
        extern char     *__progname;
 
-       fprintf(stderr, "usage: %s [-do] -i interface server1 [... serverN]\n",
+       fprintf(stderr, "usage: %s [-do] [-C circuit-id] [-R remote-id] "
+           "-i interface\n\tdestination1 [... destinationN]\n",
            __progname);
        exit(1);
 }
@@ -598,4 +657,267 @@ get_rdomain(char *name)
 
        close(s);
        return rv;
+}
+
+ssize_t
+relayagentinfo_append(struct dhcp_packet *dp, size_t dplen)
+{
+       uint8_t         *p;
+       ssize_t          newtotal = dplen;
+       int              opttotal, optlen, i, hasinfo = 0;
+       int              maxlen, circuitlen, remotelen;
+
+       if (rai_circuit == NULL)
+               return (dplen);
+
+       p = (uint8_t *)&dp->options;
+       if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
+               note("invalid dhcp options cookie");
+               return (-1);
+       }
+
+       p += DHCP_OPTIONS_COOKIE_LEN;
+       opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
+       maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
+       if (maxlen < 1 || opttotal < 1)
+               return (dplen);
+
+       for (i = 0; i < opttotal && *p != DHO_END;) {
+               if (*p == DHO_PAD)
+                       optlen = 1;
+               else
+                       optlen = p[1] + 2;
+
+               if ((i + optlen) > opttotal) {
+                       note("truncated dhcp options");
+                       return (-1);
+               }
+
+               if (*p == DHO_RELAY_AGENT_INFORMATION) {
+                       hasinfo = 1;
+                       continue;
+               }
+
+               p += optlen;
+               i += optlen;
+
+               /* We reached the end, append the relay agent info. */
+               if (*p == DHO_END || i >= opttotal) {
+                       /* We already have the Relay Agent Info, skip it. */
+                       if (hasinfo)
+                               continue;
+
+                       circuitlen = strlen(rai_circuit);
+                       if (rai_remote != NULL)
+                               remotelen = strlen(rai_remote);
+                       else
+                               remotelen = 0;
+
+                       *p++ = DHO_RELAY_AGENT_INFORMATION;
+                       if (rai_remote != NULL)
+                               *p++ = (2 + circuitlen) + (2 + remotelen);
+                       else
+                               *p++ = (2 + circuitlen);
+
+                       if ((newtotal + (2 * 3) + circuitlen + remotelen) >
+                           maxlen) {
+                               warning("no space for relay agent info");
+                               return (newtotal);
+                       }
+
+                       /* New option header: 2 bytes. */
+                       newtotal += 2;
+
+                       /* Sub-option circuit-id header plus value. */
+                       *p++ = RAI_CIRCUIT_ID;
+                       *p++ = circuitlen;
+                       memcpy(p, rai_circuit, circuitlen);
+                       p += circuitlen;
+                       newtotal += 2 + circuitlen;
+
+                       /* Optionally sub-option remote-id header plus value. */
+                       if (rai_remote != NULL) {
+                               *p++ = RAI_REMOTE_ID;
+                               *p++ = remotelen;
+                               memcpy(p, rai_remote, remotelen);
+                               p += remotelen;
+                               newtotal += 2 + remotelen;
+                       }
+
+                       *p = DHO_END;
+               }
+       }
+
+       return (newtotal);
+}
+
+int
+relayagentinfo_cmp(uint8_t *p, int plen)
+{
+       int              len;
+       char             buf[256];
+
+       len = *(p + 1);
+       if (len > plen)
+               return (-1);
+
+       switch (*p) {
+       case RAI_CIRCUIT_ID:
+               if (rai_circuit == NULL)
+                       return (-1);
+
+               memcpy(buf, p + 2, len);
+               buf[len] = 0;
+
+               return (strcmp(rai_circuit, buf));
+
+       case RAI_REMOTE_ID:
+               if (rai_remote == NULL)
+                       return (-1);
+
+               memcpy(buf, p + 2, len);
+               buf[len] = 0;
+
+               return (strcmp(rai_remote, buf));
+
+       default:
+               /* Unmatched type */
+               note("unmatched relay info %d", *p);
+               return (0);
+       }
+}
+
+ssize_t
+relayagentinfo_remove(struct dhcp_packet *dp, size_t dplen)
+{
+       uint8_t         *p, *np, *startp, *endp;
+       int              opttotal, optleft;
+       int              suboptlen, optlen, i;
+       int              remaining, matched = 0;
+
+       startp = (uint8_t *)dp;
+       p = (uint8_t *)&dp->options;
+       if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) {
+               note("invalid dhcp options cookie");
+               return (-1);
+       }
+
+       opttotal = dplen - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
+       optleft = opttotal;
+
+       p += DHCP_OPTIONS_COOKIE_LEN;
+       endp = p + opttotal;
+
+       for (i = 0; i < opttotal && *p != DHO_END;) {
+               if (*p == DHO_PAD)
+                       optlen = 1;
+               else
+                       optlen = p[1] + 2;
+
+               if ((i + optlen) > opttotal) {
+                       note("truncated dhcp options");
+                       return (-1);
+               }
+
+               if (*p == DHO_RELAY_AGENT_INFORMATION) {
+                       /* Fast case: there is no next option. */
+                       np = p + optlen;
+                       if (*np == DHO_END) {
+                               *p = *np;
+                               endp = p + 1;
+                               return (endp - startp);
+                       }
+
+                       remaining = optlen;
+                       while (remaining > 0) {
+                               suboptlen = *(p + 1);
+                               remaining -= 2 + suboptlen;
+
+                               matched = 1;
+                               if (relayagentinfo_cmp(p, suboptlen) == 0)
+                                       continue;
+
+                               matched = 0;
+                               break;
+                       }
+                       /* It is not ours Relay Agent Info, don't remove it. */
+                       if (matched == 0)
+                               break;
+
+                       /* Move the other options on top of this one. */
+                       optleft -= optlen;
+                       endp -= optlen;
+
+                       /* Replace the old agent relay info. */
+                       memmove(p, dp, optleft);
+
+                       return (endp - p);
+               }
+
+               p += optlen;
+               i += optlen;
+               optleft -= optlen;
+       }
+
+       return (endp - startp);
+}
+
+void
+l2relay(struct interface_info *ip, struct dhcp_packet *dp, int length,
+    struct packet_ctx *pc)
+{
+       struct server_list      *sp;
+       ssize_t                  dplen;
+
+       if (dp->hlen > sizeof(dp->chaddr)) {
+               note("Discarding packet with invalid hlen.");
+               return;
+       }
+
+       switch (dp->op) {
+       case BOOTREQUEST:
+               /* Add the relay agent info asked by the user. */
+               if ((dplen = relayagentinfo_append(dp, length)) == -1)
+                       return;
+
+               /*
+                * Re-send the packet to every interface except the one
+                * it came in.
+                */
+               for (sp = servers; sp != NULL; sp = sp->next) {
+                       if (sp->intf == ip)
+                               continue;
+
+                       debug("forwarded BOOTREQUEST for %s to %s",
+                           print_hw_addr(pc->pc_htype, pc->pc_hlen,
+                           pc->pc_smac), sp->intf->name);
+
+                       send_packet(sp->intf, dp, dplen, pc);
+               }
+               if (ip != interfaces) {
+                       debug("forwarded BOOTREQUEST for %s to %s",
+                           print_hw_addr(pc->pc_htype, pc->pc_hlen,
+                           pc->pc_smac), interfaces->name);
+
+                       send_packet(interfaces, dp, dplen, pc);
+               }
+               break;
+
+       case BOOTREPLY:
+               /* Remove relay agent info on offer. */
+               if ((dplen = relayagentinfo_remove(dp, length)) == -1)
+                       return;
+
+               if (ip != interfaces) {
+                       debug("forwarded BOOTREPLY for %s to %s",
+                           print_hw_addr(pc->pc_htype, pc->pc_hlen,
+                           pc->pc_dmac), interfaces->name);
+                       send_packet(interfaces, dp, dplen, pc);
+               }
+               break;
+
+       default:
+               debug("invalid operation type '%d'", dp->op);
+               return;
+       }
 }
Index: dispatch.c
===================================================================
RCS file: /home/obsdcvs/src/usr.sbin/dhcrelay/dispatch.c,v
retrieving revision 1.14
diff -u -p -r1.14 dispatch.c
--- dispatch.c  8 Dec 2016 19:18:15 -0000       1.14
+++ dispatch.c  10 Dec 2016 01:52:46 -0000
@@ -74,7 +74,8 @@ void (*bootp_packet_handler)(struct inte
 static int interface_status(struct interface_info *ifinfo);
 
 struct interface_info *
-get_interface(const char *ifname, void (*handler)(struct protocol *))
+get_interface(const char *ifname, void (*handler)(struct protocol *),
+    int isserver)
 {
        struct interface_info           *iface;
        struct ifaddrs                  *ifap, *ifa;
@@ -145,7 +146,7 @@ get_interface(const char *ifname, void (
                error("interface name '%s' too long", ifname);
 
        /* Register the interface... */
-       if_register_receive(iface);
+       if_register_receive(iface, isserver);
        if_register_send(iface);
        add_protocol(iface->name, iface->rfdesc, handler, iface);
 

Reply via email to