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?

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       9 Dec 2016 09:03:44 -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     9 Dec 2016 09:03:44 -0000
@@ -39,6 +39,12 @@
  * Enterprises, see ``http://www.vix.com''.
  */
 
+enum dhcp_relay_mode {
+       DRM_UNKNOWN,
+       DRM_LAYER2,
+       DRM_LAYER3,
+};
+
 #define        SERVER_PORT     67
 #define        CLIENT_PORT     68
 
@@ -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  9 Dec 2016 09:05:13 -0000
@@ -45,6 +45,8 @@
 .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
 .Sh DESCRIPTION
@@ -62,6 +64,11 @@ forwards it to the list of DHCP servers 
 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,7 +80,7 @@ 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
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  9 Dec 2016 09:03:44 -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 [-CdoR] "
+           "-i interface server1 [... serverN]\n",
            __progname);
        exit(1);
 }
@@ -598,4 +657,259 @@ 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;
+       size_t           opttotal;
+       int              optlen, i, hasinfo = 0;
+       int              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;
+
+       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);
+
+                       newtotal += 2;
+
+                       *p++ = RAI_CIRCUIT_ID;
+                       *p++ = circuitlen;
+                       memcpy(p, rai_circuit, circuitlen);
+                       p += circuitlen;
+                       newtotal += 2 + circuitlen;
+
+                       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];
+
+       switch (*p) {
+       case RAI_CIRCUIT_ID:
+               len = *(p + 1);
+               memcpy(buf, p + 2, len);
+               buf[len + 1] = 0;
+
+               return (strcmp(rai_circuit, buf));
+
+       case RAI_REMOTE_ID:
+               len = *(p + 1);
+               memcpy(buf, p + 2, len);
+               buf[len + 1] = 0;
+
+               return (strcmp(rai_circuit, 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;
+       size_t           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;
+
+               /*
+                * VMware PXE "ROMs" confuse the DHCP gateway address
+                * with the IP gateway address. This is a problem if your
+                * DHCP relay is running on something that's not your
+                * network gateway.
+                *
+                * It is purely informational from the relay to the client
+                * so we can safely clear it.
+                */
+               dp->giaddr.s_addr = 0x0;
+
+               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  9 Dec 2016 09:03:44 -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