/* -*- mode: c; c-file-style: "openbsd" -*- */ /* * Copyright (c) 2012 Vincent Bernat * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "lldpd.h" #include "trace.h" #include #include #include #include #include static int lldpd_af(int af) { switch (af) { case LLDPD_AF_IPV4: return AF_INET; case LLDPD_AF_IPV6: return AF_INET6; case LLDPD_AF_LAST: return AF_MAX; default: return AF_UNSPEC; } } /* Generic ethernet interface initialization */ /** * Enable multicast on the given interface. */ void interfaces_setup_multicast(struct lldpd *cfg, const char *name, int remove) { int rc; size_t i, j; const u_int8_t *mac; const u_int8_t zero[ETHER_ADDR_LEN] = {}; for (i = 0; cfg->g_protocols[i].mode != 0; i++) { if (!cfg->g_protocols[i].enabled) continue; for (j = 0; j < sizeof(cfg->g_protocols[0].mac) / sizeof(cfg->g_protocols[0].mac[0]); j++) { mac = cfg->g_protocols[i].mac[j]; if (memcmp(mac, zero, ETHER_ADDR_LEN) == 0) break; if ((rc = priv_iface_multicast(name, mac, !remove)) != 0) { errno = rc; if (errno != ENOENT) log_debug("interfaces", "unable to %s %s address to multicast filter for %s (%s)", (remove) ? "delete" : "add", cfg->g_protocols[i].name, name, strerror(rc)); } } } } /** * Free an interface. * * @param iff interface to be freed */ void interfaces_free_device(struct interfaces_device *iff) { if (!iff) return; free(iff->name); free(iff->alias); free(iff->address); free(iff->driver); free(iff); } /** * Free a list of interfaces. * * @param ifs list of interfaces to be freed */ void interfaces_free_devices(struct interfaces_device_list *ifs) { struct interfaces_device *iff, *iff_next; if (!ifs) return; for (iff = TAILQ_FIRST(ifs); iff != NULL; iff = iff_next) { iff_next = TAILQ_NEXT(iff, next); interfaces_free_device(iff); } free(ifs); } /** * Free one address * * @param ifaddr Address to be freed */ void interfaces_free_address(struct interfaces_address *ifaddr) { free(ifaddr); } /** * Free a list of addresses. * * @param ifaddrs list of addresses */ void interfaces_free_addresses(struct interfaces_address_list *ifaddrs) { struct interfaces_address *ifa, *ifa_next; if (!ifaddrs) return; for (ifa = TAILQ_FIRST(ifaddrs); ifa != NULL; ifa = ifa_next) { ifa_next = TAILQ_NEXT(ifa, next); interfaces_free_address(ifa); } free(ifaddrs); } /** * Find the appropriate interface from the name. * * @param interfaces List of available interfaces * @param device Name of the device we search for * @return The interface or NULL if not found */ struct interfaces_device * interfaces_nametointerface(struct interfaces_device_list *interfaces, const char *device) { struct interfaces_device *iface; TAILQ_FOREACH (iface, interfaces, next) { if (!strncmp(iface->name, device, IFNAMSIZ)) return iface; } log_debug("interfaces", "cannot get interface for index %s", device); return NULL; } /** * Find the appropriate interface from the index. * * @param interfaces List of available interfaces * @param index Index of the device we search for * @return The interface or NULL if not found */ struct interfaces_device * interfaces_indextointerface(struct interfaces_device_list *interfaces, int index) { struct interfaces_device *iface; TAILQ_FOREACH (iface, interfaces, next) { if (iface->index == index) return iface; } log_debug("interfaces", "cannot get interface for index %d", index); return NULL; } void interfaces_helper_allowlist(struct lldpd *cfg, struct interfaces_device_list *interfaces) { struct interfaces_device *iface; if (!cfg->g_config.c_iface_pattern) return; TAILQ_FOREACH (iface, interfaces, next) { int m = pattern_match(iface->name, cfg->g_config.c_iface_pattern, 0); switch (m) { case PATTERN_MATCH_DENIED: log_debug("interfaces", "deny %s", iface->name); iface->ignore = 1; continue; case PATTERN_MATCH_ALLOWED_EXACT: log_debug("interfaces", "allow %s (consider it as a physical interface)", iface->name); iface->type |= IFACE_PHYSICAL_T; continue; } } } #ifdef ENABLE_DOT1 static void iface_append_vlan(struct lldpd *cfg, struct interfaces_device *vlan, struct interfaces_device *lower) { struct lldpd_hardware *hardware = lldpd_get_hardware(cfg, lower->name, lower->index); struct lldpd_port *port; struct lldpd_vlan *v; char *name = NULL; uint16_t vlan_id; if (hardware == NULL) { log_debug("interfaces", "cannot find real interface %s for VLAN %s", lower->name, vlan->name); return; } port = &hardware->h_lport; for (int i = 0; (i < VLAN_BITMAP_LEN); i++) { if (vlan->vlan_bmap[i] == 0) continue; for (unsigned bit = 0; bit < 32; bit++) { uint32_t mask = 1L << bit; if (!(vlan->vlan_bmap[i] & mask)) continue; vlan_id = (i * 32) + bit; if (asprintf(&name, "vlan%d", vlan_id) == -1) return; /* Check if the VLAN is already here. */ TAILQ_FOREACH (v, &port->p_vlans, v_entries) if (strncmp(name, v->v_name, IFNAMSIZ) == 0) { free(name); return; } if ((v = (struct lldpd_vlan *)calloc(1, sizeof(struct lldpd_vlan))) == NULL) { free(name); return; } v->v_name = name; v->v_vid = vlan_id; if (vlan->pvid) port->p_pvid = vlan->pvid; log_debug("interfaces", "append VLAN %s for %s", v->v_name, hardware->h_ifname); TAILQ_INSERT_TAIL(&port->p_vlans, v, v_entries); } } } /** * Append VLAN to the lowest possible interface. * * @param vlan The VLAN interface (used to get VLAN ID). * @param upper The upper interface we are currently examining. * @param depth Depth of the stack (avoid infinite recursion) * * Initially, upper == vlan. This function will be called recursively. */ static void iface_append_vlan_to_lower(struct lldpd *cfg, struct interfaces_device_list *interfaces, struct interfaces_device *vlan, struct interfaces_device *upper, int depth) { if (depth > 5) { log_warnx("interfaces", "BUG: maximum depth reached when applying VLAN %s (loop?)", vlan->name); return; } depth++; struct interfaces_device *lower; log_debug("interfaces", "looking to apply VLAN %s to physical interface behind %s", vlan->name, upper->name); /* Some bridges managed VLAN internally, skip them. */ if (upper->type & IFACE_BRIDGE_VLAN_T) { log_debug("interfaces", "VLAN %s ignored for VLAN-aware bridge interface %s", vlan->name, upper->name); return; } /* Easy: check if we have a lower interface. */ if (upper->lower) { log_debug("interfaces", "VLAN %s on lower interface %s", vlan->name, upper->name); iface_append_vlan_to_lower(cfg, interfaces, vlan, upper->lower, depth); return; } /* Other easy case, we have a physical interface. */ if (upper->type & IFACE_PHYSICAL_T) { log_debug("interfaces", "VLAN %s on physical interface %s", vlan->name, upper->name); iface_append_vlan(cfg, vlan, upper); return; } /* We can now search for interfaces that have our interface as an upper * interface. */ TAILQ_FOREACH (lower, interfaces, next) { if (lower->upper != upper) continue; log_debug("interfaces", "VLAN %s on lower interface %s", vlan->name, upper->name); iface_append_vlan_to_lower(cfg, interfaces, vlan, lower, depth); } } void interfaces_helper_vlan(struct lldpd *cfg, struct interfaces_device_list *interfaces) { struct interfaces_device *iface; TAILQ_FOREACH (iface, interfaces, next) { if (!(iface->type & IFACE_VLAN_T) && bitmap_isempty(iface->vlan_bmap)) continue; /* We need to find the physical interfaces of this vlan, through bonds and bridges. */ log_debug("interfaces", "search physical interface for VLAN interface %s", iface->name); iface_append_vlan_to_lower(cfg, interfaces, iface, iface, 0); } } #endif /* Fill out chassis ID if not already done. Only physical interfaces are * considered. */ void interfaces_helper_chassis(struct lldpd *cfg, struct interfaces_device_list *interfaces) { struct interfaces_device *iface; struct lldpd_hardware *hardware; char *name = NULL; static u_int8_t zero_mac[] = { 0, 0, 0, 0, 0, 0 }; if (!cfg->g_config.c_cap_override) { LOCAL_CHASSIS(cfg)->c_cap_enabled &= ~(LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | LLDP_CAP_STATION); TAILQ_FOREACH (iface, interfaces, next) { if (iface->type & IFACE_BRIDGE_T) LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_BRIDGE; if (iface->type & IFACE_WIRELESS_T) LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_WLAN; } if ((LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_STATION) && (LOCAL_CHASSIS(cfg)->c_cap_enabled == 0)) LOCAL_CHASSIS(cfg)->c_cap_enabled = LLDP_CAP_STATION; } /* Do not modify the chassis if it's already set to a MAC address or if * it's set to a local address equal to the user-provided * configuration. */ if ((LOCAL_CHASSIS(cfg)->c_id != NULL && LOCAL_CHASSIS(cfg)->c_id_subtype == LLDP_CHASSISID_SUBTYPE_LLADDR) || cfg->g_config.c_cid_string != NULL) return; /* We already have one */ TAILQ_FOREACH (iface, interfaces, next) { if (!(iface->type & IFACE_PHYSICAL_T)) continue; if (cfg->g_config.c_cid_pattern && !pattern_match(iface->name, cfg->g_config.c_cid_pattern, 0)) continue; if ((hardware = lldpd_get_hardware(cfg, iface->name, iface->index)) == NULL) /* That's odd. Let's skip. */ continue; if (memcmp(hardware->h_lladdr, zero_mac, ETHER_ADDR_LEN) == 0) /* All-zero MAC address */ continue; name = malloc(ETHER_ADDR_LEN); if (!name) { log_warn("interfaces", "not enough memory for chassis ID"); return; } free(LOCAL_CHASSIS(cfg)->c_id); memcpy(name, hardware->h_lladdr, ETHER_ADDR_LEN); LOCAL_CHASSIS(cfg)->c_id = name; LOCAL_CHASSIS(cfg)->c_id_len = ETHER_ADDR_LEN; LOCAL_CHASSIS(cfg)->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; return; } } #undef IN_IS_ADDR_LOOPBACK #define IN_IS_ADDR_LOOPBACK(a) ((a)->s_addr == htonl(INADDR_LOOPBACK)) #undef IN_IS_ADDR_ANY #define IN_IS_ADDR_ANY(a) ((a)->s_addr == htonl(INADDR_ANY)) #undef IN_IS_ADDR_LINKLOCAL #define IN_IS_ADDR_LINKLOCAL(a) (((a)->s_addr & htonl(0xffff0000)) == htonl(0xa9fe0000)) #undef IN_IS_ADDR_GLOBAL #define IN_IS_ADDR_GLOBAL(a) \ (!IN_IS_ADDR_LOOPBACK(a) && !IN_IS_ADDR_ANY(a) && !IN_IS_ADDR_LINKLOCAL(a)) #undef IN6_IS_ADDR_GLOBAL #define IN6_IS_ADDR_GLOBAL(a) (!IN6_IS_ADDR_LOOPBACK(a) && !IN6_IS_ADDR_LINKLOCAL(a)) /* Add management addresses for the given family. We only take one of each address family, unless a pattern is provided and is not all negative. For example !*:*,!10.* will only deny addresses. We will pick the first IPv4 address not matching 10.*. */ static int interfaces_helper_mgmt_for_af(struct lldpd *cfg, int af, struct interfaces_address_list *addrs, struct interfaces_device_list *interfaces, int global, int allnegative) { struct interfaces_address *addr; struct interfaces_device *device; struct lldpd_mgmt *mgmt; char addrstrbuf[INET6_ADDRSTRLEN]; int found = 0; union lldpd_address in_addr; size_t in_addr_size; TAILQ_FOREACH (addr, addrs, next) { if (addr->address.ss_family != lldpd_af(af)) continue; switch (af) { case LLDPD_AF_IPV4: in_addr_size = sizeof(struct in_addr); memcpy(&in_addr, &((struct sockaddr_in *)&addr->address)->sin_addr, in_addr_size); if (global) { if (!IN_IS_ADDR_GLOBAL(&in_addr.inet)) continue; } else { if (!IN_IS_ADDR_LINKLOCAL(&in_addr.inet)) continue; } break; case LLDPD_AF_IPV6: in_addr_size = sizeof(struct in6_addr); memcpy(&in_addr, &((struct sockaddr_in6 *)&addr->address)->sin6_addr, in_addr_size); if (global) { if (!IN6_IS_ADDR_GLOBAL(&in_addr.inet6)) continue; } else { if (!IN6_IS_ADDR_LINKLOCAL(&in_addr.inet6)) continue; } break; default: assert(0); continue; } if (inet_ntop(lldpd_af(af), &in_addr, addrstrbuf, sizeof(addrstrbuf)) == NULL) { log_warn("interfaces", "unable to convert IP address to a string"); continue; } if (cfg->g_config.c_mgmt_pattern == NULL || /* Match on IP address */ pattern_match(addrstrbuf, cfg->g_config.c_mgmt_pattern, allnegative) || /* Match on interface name */ ((device = interfaces_indextointerface(interfaces, addr->index)) && pattern_match(device->name, cfg->g_config.c_mgmt_pattern, allnegative))) { mgmt = lldpd_alloc_mgmt(af, &in_addr, in_addr_size, addr->index); if (mgmt == NULL) { assert(errno == ENOMEM); /* anything else is a bug */ log_warn("interfaces", "out of memory error"); return found; } log_debug("interfaces", "add management address %s", addrstrbuf); TAILQ_INSERT_TAIL(&LOCAL_CHASSIS(cfg)->c_mgmt, mgmt, m_entries); found = 1; /* Don't take additional address if the pattern is all negative. */ if (allnegative) break; } } return found; } /* Find a management address in all available interfaces, even those that were already handled. This is a special interface handler because it does not really handle interface related information (management address is attached to the local chassis). */ void interfaces_helper_mgmt(struct lldpd *cfg, struct interfaces_address_list *addrs, struct interfaces_device_list *interfaces) { int allnegative = 0; int af; const char *pattern = cfg->g_config.c_mgmt_pattern; lldpd_chassis_mgmt_cleanup(LOCAL_CHASSIS(cfg)); if (!cfg->g_config.c_mgmt_advertise) return; /* Is the pattern provided an actual IP address? */ if (pattern && strpbrk(pattern, "!,*?") == NULL) { unsigned char addr[sizeof(struct in6_addr)]; size_t addr_size; struct lldpd_mgmt *mgmt; struct interfaces_address *ifaddr; for (af = LLDPD_AF_UNSPEC + 1; af != LLDPD_AF_LAST; af++) { switch (af) { case LLDPD_AF_IPV4: addr_size = sizeof(struct in_addr); break; case LLDPD_AF_IPV6: addr_size = sizeof(struct in6_addr); break; default: assert(0); } if (inet_pton(lldpd_af(af), pattern, addr) == 1) break; } if (af != LLDPD_AF_LAST) { /* Try to get the index if possible. */ TAILQ_FOREACH (ifaddr, addrs, next) { if (ifaddr->address.ss_family != lldpd_af(af)) continue; if (LLDPD_AF_IPV4 == af) { struct sockaddr_in *sa_sin; sa_sin = (struct sockaddr_in *)&ifaddr->address; if (0 == memcmp(addr, &(sa_sin->sin_addr), addr_size)) break; } else if (LLDPD_AF_IPV6 == af) { if (0 == memcmp(addr, &((struct sockaddr_in6 *)&ifaddr ->address) ->sin6_addr, addr_size)) break; } } mgmt = lldpd_alloc_mgmt(af, addr, addr_size, ifaddr ? ifaddr->index : 0); if (mgmt == NULL) { log_warn("interfaces", "out of memory error"); return; } log_debug("interfaces", "add exact management address %s", pattern); TAILQ_INSERT_TAIL(&LOCAL_CHASSIS(cfg)->c_mgmt, mgmt, m_entries); return; } /* else: could be an interface name */ } /* Is the pattern provided all negative? */ if (pattern == NULL) allnegative = 1; else if (pattern[0] == '!') { /* If each comma is followed by '!', its an all negative pattern */ const char *sep = pattern; while ((sep = strchr(sep, ',')) && (*(++sep) == '!')) ; if (sep == NULL) allnegative = 1; } /* Find management addresses */ for (af = LLDPD_AF_UNSPEC + 1; af != LLDPD_AF_LAST; af++) { (void)(interfaces_helper_mgmt_for_af(cfg, af, addrs, interfaces, 1, allnegative) || interfaces_helper_mgmt_for_af(cfg, af, addrs, interfaces, 0, allnegative)); } } /* Fill up port name and description */ void interfaces_helper_port_name_desc(struct lldpd *cfg, struct lldpd_hardware *hardware, struct interfaces_device *iface) { struct lldpd_port *port = &hardware->h_lport; /* We need to set the portid to what the client configured. This can be done from the CLI. */ int has_alias = (iface->alias != NULL && strlen(iface->alias) != 0 && strncmp("lldpd: ", iface->alias, 7)); int portid_type = cfg->g_config.c_lldp_portid_type; if (portid_type == LLDP_PORTID_SUBTYPE_IFNAME || (portid_type == LLDP_PORTID_SUBTYPE_UNKNOWN && has_alias) || (port->p_id_subtype == LLDP_PORTID_SUBTYPE_LOCAL && has_alias)) { if (port->p_id_subtype != LLDP_PORTID_SUBTYPE_LOCAL) { log_debug("interfaces", "use ifname for %s", hardware->h_ifname); port->p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; port->p_id_len = strlen(hardware->h_ifname); free(port->p_id); if ((port->p_id = calloc(1, port->p_id_len)) == NULL) fatal("interfaces", NULL); memcpy(port->p_id, hardware->h_ifname, port->p_id_len); } if (port->p_descr_force == 0) { /* use the actual alias in the port description */ log_debug("interfaces", "using alias in description for %s", hardware->h_ifname); free(port->p_descr); if (has_alias) { port->p_descr = strdup(iface->alias); } else { /* We don't have anything else to put here and for CDP * with need something non-NULL */ port->p_descr = strdup(hardware->h_ifname); } } } else { if (port->p_id_subtype != LLDP_PORTID_SUBTYPE_LOCAL) { log_debug("interfaces", "use MAC address for %s", hardware->h_ifname); port->p_id_subtype = LLDP_PORTID_SUBTYPE_LLADDR; free(port->p_id); if ((port->p_id = calloc(1, ETHER_ADDR_LEN)) == NULL) fatal("interfaces", NULL); memcpy(port->p_id, hardware->h_lladdr, ETHER_ADDR_LEN); port->p_id_len = ETHER_ADDR_LEN; } if (port->p_descr_force == 0) { /* use the ifname in the port description until alias is set */ log_debug("interfaces", "using ifname in description for %s", hardware->h_ifname); free(port->p_descr); port->p_descr = strdup(hardware->h_ifname); } } } void interfaces_helper_add_hardware(struct lldpd *cfg, struct lldpd_hardware *hardware) { TRACE(LLDPD_INTERFACES_NEW(hardware->h_ifname)); TAILQ_INSERT_TAIL(&cfg->g_hardware, hardware, h_entries); } void interfaces_helper_physical(struct lldpd *cfg, struct interfaces_device_list *interfaces, struct lldpd_ops *ops, int (*init)(struct lldpd *, struct lldpd_hardware *)) { struct interfaces_device *iface; struct lldpd_hardware *hardware; int created; TAILQ_FOREACH (iface, interfaces, next) { if (!(iface->type & IFACE_PHYSICAL_T)) continue; if (iface->ignore) continue; log_debug("interfaces", "%s is an acceptable ethernet device", iface->name); created = 0; if ((hardware = lldpd_get_hardware(cfg, iface->name, iface->index)) == NULL) { if ((hardware = lldpd_alloc_hardware(cfg, iface->name, iface->index)) == NULL) { log_warnx("interfaces", "Unable to allocate space for %s", iface->name); continue; } created = 1; } if (hardware->h_flags) continue; if (hardware->h_ops != ops || hardware->h_ifindex_changed) { if (!created) { log_debug("interfaces", "interface %s is converted from another type of interface", hardware->h_ifname); if (hardware->h_ops && hardware->h_ops->cleanup) { hardware->h_ops->cleanup(cfg, hardware); levent_hardware_release(hardware); levent_hardware_init(hardware); } } if (init(cfg, hardware) != 0) { log_warnx("interfaces", "unable to initialize %s", hardware->h_ifname); lldpd_hardware_cleanup(cfg, hardware); continue; } hardware->h_ops = ops; hardware->h_mangle = (iface->upper && iface->upper->type & IFACE_BOND_T); } if (created) interfaces_helper_add_hardware(cfg, hardware); else lldpd_port_cleanup(&hardware->h_lport, 0); hardware->h_flags = iface->flags; /* Should be non-zero */ iface->ignore = 1; /* Future handlers don't have to care about this interface. */ /* Get local address */ memcpy(&hardware->h_lladdr, iface->address, ETHER_ADDR_LEN); /* Fill information about port */ interfaces_helper_port_name_desc(cfg, hardware, iface); /* Fill additional info */ hardware->h_mtu = iface->mtu ? iface->mtu : 1500; #ifdef ENABLE_DOT3 if (iface->upper && iface->upper->type & IFACE_BOND_T) hardware->h_lport.p_aggregid = iface->upper->index; else hardware->h_lport.p_aggregid = 0; #endif } } void interfaces_helper_promisc(struct lldpd *cfg, struct lldpd_hardware *hardware) { if (!cfg->g_config.c_promisc) return; if (priv_iface_promisc(hardware->h_ifname) != 0) { log_warnx("interfaces", "unable to enable promiscuous mode for %s", hardware->h_ifname); } } /** * Send the packet using the hardware function. Optionnaly mangle the MAC address. * * With bonds, we have duplicate MAC address on different physical * interfaces. We need to alter the source MAC address when we send on an * inactive slave. The `h_mangle` flah is used to know if we need to do * something like that. */ int interfaces_send_helper(struct lldpd *cfg, struct lldpd_hardware *hardware, char *buffer, size_t size) { if (size < 2 * ETHER_ADDR_LEN) { log_warnx("interfaces", "packet to send on %s is too small!", hardware->h_ifname); return 0; } if (hardware->h_mangle) { #define MAC_UL_ADMINISTERED_BIT_MASK 0x02 char *src_mac = buffer + ETHER_ADDR_LEN; char arbitrary[] = { 0x00, 0x60, 0x08, 0x69, 0x97, 0xef }; switch (cfg->g_config.c_bond_slave_src_mac_type) { case LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED: if (!(*src_mac & MAC_UL_ADMINISTERED_BIT_MASK)) { *src_mac |= MAC_UL_ADMINISTERED_BIT_MASK; break; } /* Fallback to fixed value */ memcpy(src_mac, arbitrary, ETHER_ADDR_LEN); break; case LLDP_BOND_SLAVE_SRC_MAC_TYPE_FIXED: memcpy(src_mac, arbitrary, ETHER_ADDR_LEN); break; case LLDP_BOND_SLAVE_SRC_MAC_TYPE_ZERO: memset(src_mac, 0, ETHER_ADDR_LEN); break; } } return hardware->h_ops->send(cfg, hardware, buffer, size); }