/* $OpenBSD: dhcrelay.c,v 1.65 2019/08/06 11:07:37 krw Exp $ */ /* * Copyright (c) 2004 Henning Brauer * Copyright (c) 1997, 1998, 1999 The Internet Software Consortium. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of The Internet Software Consortium nor the names * of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This software has been written for the Internet Software Consortium * by Ted Lemon in cooperation with Vixie * Enterprises. To learn more about the Internet Software Consortium, * see ``http://www.vix.com/isc''. To learn more about Vixie * Enterprises, see ``http://www.vix.com''. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dhcp.h" #include "dhcpd.h" #include "log.h" 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 *); void relay_agentinfo(struct packet_ctx *, struct interface_info *, int); int relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *, int); ssize_t relay_agentinfo_append(struct packet_ctx *, struct dhcp_packet *, size_t); ssize_t relay_agentinfo_remove(struct packet_ctx *, struct dhcp_packet *, size_t); time_t cur_time; struct interface_info *interfaces = NULL; struct server_list *servers; struct iflist intflist; int server_fd; int oflag; enum dhcp_relay_mode drm = DRM_UNKNOWN; const char *rai_circuit = NULL; const char *rai_remote = NULL; int rai_replace = 0; int main(int argc, char *argv[]) { int ch, devnull = -1, daemonize, opt, rdomain; struct server_list *sp = NULL; struct passwd *pw; struct sockaddr_in laddr; int optslen; daemonize = 1; log_init(1, LOG_DAEMON); /* log to stderr until daemonized */ setup_iflist(); while ((ch = getopt(argc, argv, "aC:di:oR:r")) != -1) { switch (ch) { case 'C': rai_circuit = optarg; break; case 'd': daemonize = 0; break; case 'i': if (interfaces != NULL) usage(); interfaces = iflist_getbyname(optarg); if (interfaces == NULL) fatalx("interface '%s' not found", optarg); break; case 'o': /* add the relay agent information option */ oflag++; break; case 'R': rai_remote = optarg; break; case 'r': rai_replace = 1; break; default: usage(); /* not reached */ } } argc -= optind; argv += optind; if (argc < 1) usage(); if (rai_remote != NULL && rai_circuit == NULL) fatalx("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 > DHCP_OPTION_MAXLEN) fatalx("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) fatalx("calloc"); if ((sp->intf = register_interface(argv[0], got_one, 1)) != NULL) { if (drm == DRM_LAYER3) fatalx("don't mix interfaces with hosts"); if (sp->intf->hw_address.htype == HTYPE_IPSEC_TUNNEL) fatalx("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 { he = gethostbyname(argv[0]); if (!he) log_warnx("%s: host unknown", argv[0]); else iap = ((struct in_addr *)he->h_addr_list[0]); } if (iap) { if (drm == DRM_LAYER2) fatalx("don't mix interfaces with hosts"); drm = DRM_LAYER3; sp->next = servers; servers = sp; memcpy(&ss2sin(&sp->to)->sin_addr, iap, sizeof(*iap)); } else free(sp); argc--; argv++; } if (daemonize) { devnull = open(_PATH_DEVNULL, O_RDWR, 0); if (devnull == -1) fatal("open(%s)", _PATH_DEVNULL); } if (interfaces == NULL || register_interface(interfaces->name, got_one, 0) == NULL) fatalx("no interface given"); /* We need an address for running layer 3 mode. */ if (drm == DRM_LAYER3 && (interfaces->hw_address.htype != HTYPE_IPSEC_TUNNEL && interfaces->primary_address.s_addr == 0)) fatalx("interface '%s' does not have an address", interfaces->name); /* We need at least one server. */ if (!sp) usage(); rdomain = get_rdomain(interfaces->name); /* Enable the relay agent option by default for enc0 */ if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) oflag++; bzero(&laddr, sizeof laddr); laddr.sin_len = sizeof laddr; laddr.sin_family = AF_INET; laddr.sin_port = htons(SERVER_PORT); 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; ss2sin(&sp->to)->sin_port = htons(SERVER_PORT); ss2sin(&sp->to)->sin_family = AF_INET; ss2sin(&sp->to)->sin_len = sizeof(struct sockaddr_in); sp->fd = socket(AF_INET, SOCK_DGRAM, 0); if (sp->fd == -1) fatal("socket"); opt = 1; if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) == -1) fatal("setsockopt"); if (setsockopt(sp->fd, SOL_SOCKET, SO_RTABLE, &rdomain, sizeof(rdomain)) == -1) fatal("setsockopt"); if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) == -1) fatal("bind"); if (connect(sp->fd, (struct sockaddr *)&sp->to, sp->to.ss_len) == -1) fatal("connect"); add_protocol("server", sp->fd, got_response, sp); } /* Socket used to forward packets to the DHCP client */ if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) { laddr.sin_addr.s_addr = INADDR_ANY; server_fd = socket(AF_INET, SOCK_DGRAM, 0); if (server_fd == -1) fatal("socket"); opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) == -1) fatal("setsockopt"); if (setsockopt(server_fd, SOL_SOCKET, SO_RTABLE, &rdomain, sizeof(rdomain)) == -1) fatal("setsockopt"); if (bind(server_fd, (struct sockaddr *)&laddr, sizeof(laddr)) == -1) fatal("bind"); } tzset(); time(&cur_time); if (drm == DRM_LAYER3) bootp_packet_handler = relay; else bootp_packet_handler = l2relay; if ((pw = getpwnam("_dhcp")) == NULL) fatalx("user \"_dhcp\" not found"); if (chroot(pw->pw_dir) == -1) fatal("chroot"); if (chdir("/") == -1) fatal("chdir(\"/\")"); if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("can't drop privileges"); if (daemonize) { if (rdaemon(devnull) == -1) fatal("rdaemon"); log_init(0, LOG_DAEMON); /* stop logging to stderr */ } if (pledge("stdio route", NULL) == -1) fatalx("pledge"); dispatch(); /* not reached */ exit(0); } void relay(struct interface_info *ip, struct dhcp_packet *packet, int length, struct packet_ctx *pc) { struct server_list *sp; struct sockaddr_in to; if (packet->hlen > sizeof packet->chaddr) { log_info("Discarding packet with invalid hlen."); return; } /* If it's a bootreply, forward it to the client. */ if (packet->op == BOOTREPLY) { /* Filter packet that were not meant for us. */ if (packet->giaddr.s_addr != interfaces->primary_address.s_addr) return; bzero(&to, sizeof(to)); if (!(packet->flags & htons(BOOTP_BROADCAST))) { to.sin_addr = packet->yiaddr; to.sin_port = htons(CLIENT_PORT); } else { to.sin_addr.s_addr = htonl(INADDR_BROADCAST); to.sin_port = htons(CLIENT_PORT); } to.sin_family = AF_INET; to.sin_len = sizeof to; *ss2sin(&pc->pc_dst) = to; /* * Set up the hardware destination address. If it's a reply * with the BROADCAST flag set, we should send an L2 broad- * cast as well. */ if (!(packet->flags & htons(BOOTP_BROADCAST))) { pc->pc_hlen = packet->hlen; if (pc->pc_hlen > CHADDR_SIZE) pc->pc_hlen = CHADDR_SIZE; memcpy(pc->pc_dmac, packet->chaddr, pc->pc_hlen); pc->pc_htype = packet->htype; } else { memset(pc->pc_dmac, 0xff, sizeof(pc->pc_dmac)); } relay_agentinfo(pc, interfaces, packet->op); if ((length = relay_agentinfo_remove(pc, packet, length)) == -1) { log_info("ignoring BOOTREPLY with invalid " "relay agent information"); 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. */ packet->giaddr.s_addr = 0x0; ss2sin(&pc->pc_src)->sin_addr = interfaces->primary_address; if (send_packet(interfaces, packet, length, pc) != -1) log_debug("forwarded BOOTREPLY for %s to %s", print_hw_addr(packet->htype, packet->hlen, packet->chaddr), inet_ntoa(to.sin_addr)); return; } if (ip == NULL) { log_info("ignoring non BOOTREPLY from server"); return; } if (packet->hops > 16) { log_info("ignoring BOOTREQUEST with hop count of %d", packet->hops); return; } packet->hops++; /* * Set the giaddr so the server can figure out what net it's * from and so that we can later forward the response to the * correct net. The RFC specifies that we have to keep the * initial giaddr (in case we relay over multiple hops). */ if (!packet->giaddr.s_addr) packet->giaddr = ip->primary_address; relay_agentinfo(pc, interfaces, packet->op); if ((length = relay_agentinfo_append(pc, packet, length)) == -1) { log_info("ignoring BOOTREQUEST with invalid " "relay agent information"); return; } /* Otherwise, it's a BOOTREQUEST, so forward it to all the servers. */ for (sp = servers; sp; sp = sp->next) { if (send(sp->fd, packet, length, 0) != -1) { log_debug("forwarded BOOTREQUEST for %s to %s", print_hw_addr(packet->htype, packet->hlen, packet->chaddr), inet_ntoa(ss2sin(&sp->to)->sin_addr)); } } } void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-dor] [-C circuit-id] [-R remote-id] " "-i interface\n\tdestination ...\n", __progname); exit(1); } int rdaemon(int devnull) { if (devnull == -1) { errno = EBADF; return (-1); } if (fcntl(devnull, F_GETFL) == -1) return (-1); switch (fork()) { case -1: return (-1); case 0: break; default: _exit(0); } if (setsid() == -1) return (-1); (void)dup2(devnull, STDIN_FILENO); (void)dup2(devnull, STDOUT_FILENO); (void)dup2(devnull, STDERR_FILENO); if (devnull > 2) (void)close(devnull); return (0); } char * print_hw_addr(int htype, int hlen, unsigned char *data) { static char habuf[49]; char *s = habuf; int i, j, slen = sizeof(habuf); if (htype == 0 || hlen == 0) { bad: strlcpy(habuf, "", sizeof habuf); return habuf; } for (i = 0; i < hlen; i++) { j = snprintf(s, slen, "%02x", data[i]); if (j <= 0 || j >= slen) goto bad; j = strlen (s); s += j; slen -= (j + 1); *s++ = ':'; } *--s = '\0'; return habuf; } void got_response(struct protocol *l) { struct packet_ctx pc; ssize_t result; union { /* * Packet input buffer. Must be as large as largest * possible MTU. */ unsigned char packbuf[4095]; struct dhcp_packet packet; } u; struct server_list *sp = l->local; memset(&u, DHO_END, sizeof(u)); if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 && errno != ECONNREFUSED) { /* * Ignore ECONNREFUSED as too many dhcp servers send a bogus * icmp unreach for every request. */ log_warn("recv failed for %s", inet_ntoa(ss2sin(&sp->to)->sin_addr)); return; } if (result == -1 && errno == ECONNREFUSED) return; if (result == 0) return; if (result < BOOTP_MIN_LEN) { log_info("Discarding packet with invalid size."); return; } memset(&pc, 0, sizeof(pc)); pc.pc_src.ss_family = AF_INET; pc.pc_src.ss_len = sizeof(struct sockaddr_in); memcpy(&ss2sin(&pc.pc_src)->sin_addr, &ss2sin(&sp->to)->sin_addr, sizeof(ss2sin(&pc.pc_src)->sin_addr)); ss2sin(&pc.pc_src)->sin_port = htons(SERVER_PORT); pc.pc_dst.ss_family = AF_INET; pc.pc_dst.ss_len = sizeof(struct sockaddr_in); ss2sin(&pc.pc_dst)->sin_port = htons(CLIENT_PORT); if (bootp_packet_handler) (*bootp_packet_handler)(NULL, &u.packet, result, &pc); } void relay_agentinfo(struct packet_ctx *pc, struct interface_info *intf, int bootop) { static u_int8_t buf[8]; struct sockaddr_in *sin; if (oflag == 0) return; if (rai_remote != NULL) { pc->pc_remote = rai_remote; pc->pc_remotelen = strlen(rai_remote); } else pc->pc_remotelen = 0; if (rai_circuit == NULL) { buf[0] = (uint8_t)(intf->index << 8); buf[1] = intf->index & 0xff; pc->pc_circuit = buf; pc->pc_circuitlen = 2; if (rai_remote == NULL) { if (bootop == BOOTREPLY) sin = ss2sin(&pc->pc_dst); else sin = ss2sin(&pc->pc_src); pc->pc_remote = (uint8_t *)&sin->sin_addr; pc->pc_remotelen = sizeof(sin->sin_addr); } } else { pc->pc_circuit = rai_circuit; pc->pc_circuitlen = strlen(rai_circuit); } } int relay_agentinfo_cmp(struct packet_ctx *pc, uint8_t *p, int plen) { int len; char buf[256]; if (oflag == 0) return (-1); len = *(p + 1); if (len > plen) return (-1); switch (*p) { case RAI_CIRCUIT_ID: if (pc->pc_circuit == NULL) return (-1); if (pc->pc_circuitlen != len) return (-1); memcpy(buf, p + DHCP_OPTION_HDR_LEN, len); return (memcmp(pc->pc_circuit, buf, len)); case RAI_REMOTE_ID: if (pc->pc_remote == NULL) return (-1); if (pc->pc_remotelen != len) return (-1); memcpy(buf, p + DHCP_OPTION_HDR_LEN, len); return (memcmp(pc->pc_remote, buf, len)); default: /* Unmatched type */ log_info("unmatched relay info %d", *p); return (0); } } ssize_t relay_agentinfo_append(struct packet_ctx *pc, struct dhcp_packet *dp, size_t dplen) { uint8_t *p, *startp; ssize_t newtotal = dplen; int opttotal, optlen, i, hasinfo = 0; int maxlen, neededlen; /* Only append when enabled. */ if (oflag == 0) return (dplen); startp = (uint8_t *)dp; p = (uint8_t *)&dp->options; if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) { log_info("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] + DHCP_OPTION_HDR_LEN; if ((i + optlen) > opttotal) { log_info("truncated dhcp options"); return (-1); } if (*p == DHO_RELAY_AGENT_INFORMATION) { if (rai_replace) { memmove(p, p + optlen, opttotal - i); opttotal -= optlen; optlen = 0; } else hasinfo = 1; } p += optlen; i += optlen; /* We reached the end, append the relay agent info. */ if (i < opttotal && *p == DHO_END) { /* We already have the Relay Agent Info, skip it. */ if (hasinfo) continue; /* Calculate needed length to append new data. */ neededlen = newtotal + DHCP_OPTION_HDR_LEN; if (pc->pc_circuitlen > 0) neededlen += DHCP_OPTION_HDR_LEN + pc->pc_circuitlen; if (pc->pc_remotelen > 0) neededlen += DHCP_OPTION_HDR_LEN + pc->pc_remotelen; /* Save one byte for DHO_END. */ neededlen += 1; /* Check if we have space for the new options. */ if (neededlen > maxlen) { log_warnx("no space for relay agent info"); return (newtotal); } /* New option header: 2 bytes. */ newtotal += DHCP_OPTION_HDR_LEN; *p++ = DHO_RELAY_AGENT_INFORMATION; *p = 0; if (pc->pc_circuitlen > 0) { newtotal += DHCP_OPTION_HDR_LEN + pc->pc_circuitlen; *p = (*p) + DHCP_OPTION_HDR_LEN + pc->pc_circuitlen; } if (pc->pc_remotelen > 0) { newtotal += DHCP_OPTION_HDR_LEN + pc->pc_remotelen; *p = (*p) + DHCP_OPTION_HDR_LEN + pc->pc_remotelen; } p++; /* Sub-option circuit-id header plus value. */ if (pc->pc_circuitlen > 0) { *p++ = RAI_CIRCUIT_ID; *p++ = pc->pc_circuitlen; memcpy(p, pc->pc_circuit, pc->pc_circuitlen); p += pc->pc_circuitlen; } /* Sub-option remote-id header plus value. */ if (pc->pc_remotelen > 0) { *p++ = RAI_REMOTE_ID; *p++ = pc->pc_remotelen; memcpy(p, pc->pc_remote, pc->pc_remotelen); p += pc->pc_remotelen; } *p = DHO_END; } } /* Zero the padding so we don't leak anything. */ p++; if (p < (startp + maxlen)) memset(p, 0, (startp + maxlen) - p); return (newtotal); } ssize_t relay_agentinfo_remove(struct packet_ctx *pc, struct dhcp_packet *dp, size_t dplen) { uint8_t *p, *np, *startp, *endp; int opttotal, optleft; int suboptlen, optlen, i; int maxlen, remaining, matched = 0; startp = (uint8_t *)dp; p = (uint8_t *)&dp->options; if (memcmp(p, DHCP_OPTIONS_COOKIE, DHCP_OPTIONS_COOKIE_LEN)) { log_info("invalid dhcp options cookie"); return (-1); } maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 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] + DHCP_OPTION_HDR_LEN; if ((i + optlen) > opttotal) { log_info("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; /* Zero the padding so we don't leak data. */ if (endp < (startp + maxlen)) memset(endp, 0, (startp + maxlen) - endp); return (dplen); } remaining = optlen; while (remaining > 0) { suboptlen = *(p + 1); remaining -= DHCP_OPTION_HDR_LEN + suboptlen; matched = 1; if (relay_agentinfo_cmp(pc, 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); endp++; /* Zero the padding so we don't leak data. */ if (endp < (startp + maxlen)) memset(endp, 0, (startp + maxlen) - endp); return (endp - startp); } p += optlen; i += optlen; optleft -= optlen; } return (endp - startp); } int get_rdomain(char *name) { int rv = 0, s; struct ifreq ifr; if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) fatal("get_rdomain socket"); bzero(&ifr, sizeof(ifr)); strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1) rv = ifr.ifr_rdomainid; close(s); return rv; } 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)) { log_info("Discarding packet with invalid hlen."); return; } relay_agentinfo(pc, ip, dp->op); switch (dp->op) { case BOOTREQUEST: /* Add the relay agent info asked by the user. */ if ((dplen = relay_agentinfo_append(pc, 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; log_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) { log_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 = relay_agentinfo_remove(pc, dp, length)) == -1) return; if (ip != interfaces) { log_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: log_debug("invalid operation type '%d'", dp->op); return; } }