diff options
Diffstat (limited to 'agent/mibgroup/ip-mib/data_access/ipaddress_ioctl.c')
-rw-r--r-- | agent/mibgroup/ip-mib/data_access/ipaddress_ioctl.c | 746 |
1 files changed, 746 insertions, 0 deletions
diff --git a/agent/mibgroup/ip-mib/data_access/ipaddress_ioctl.c b/agent/mibgroup/ip-mib/data_access/ipaddress_ioctl.c new file mode 100644 index 0000000..cf65013 --- /dev/null +++ b/agent/mibgroup/ip-mib/data_access/ipaddress_ioctl.c @@ -0,0 +1,746 @@ +/* + * Interface MIB architecture support + * + * $Id$ + */ +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-features.h> +#include <net-snmp/net-snmp-includes.h> +#include "mibII/mibII_common.h" + +#include <net-snmp/agent/net-snmp-agent-includes.h> +#include <net-snmp/data_access/ipaddress.h> +#include <net-snmp/data_access/interface.h> + +#include "ip-mib/ipAddressTable/ipAddressTable_constants.h" +#include "if-mib/data_access/interface_ioctl.h" + +#include <errno.h> +#include <net/if.h> +#include <sys/ioctl.h> + +#include "ipaddress_ioctl.h" + +netsnmp_feature_child_of(ipadress_ioctl_entry_copy, ipaddress_common) + +static void _print_flags(short flags); + +#define LIST_TOKEN "ioctl_extras" + +/* + * get extra structure + * + * @returns the extras structure from the entry + */ +_ioctl_extras * +netsnmp_ioctl_ipaddress_extras_get(netsnmp_ipaddress_entry *entry) +{ + if ((NULL == entry) || (NULL == entry->arch_data)) + return NULL; + + return (_ioctl_extras*)netsnmp_get_list_data(entry->arch_data, LIST_TOKEN); +} + +/** + * initialize ioctl extras + * + * @returns _ioctl_extras pointer, or NULL on error + */ +_ioctl_extras * +netsnmp_ioctl_ipaddress_entry_init(netsnmp_ipaddress_entry *entry) +{ + netsnmp_data_list *node; + _ioctl_extras *extras; + + if (NULL == entry) + return NULL; + + extras = SNMP_MALLOC_TYPEDEF(_ioctl_extras); + if (NULL == extras) + return NULL; + + node = netsnmp_create_data_list(LIST_TOKEN, extras, free); + if (NULL == node) { + free(extras); + return NULL; + } + + netsnmp_data_list_add_node( &entry->arch_data, node ); + + return extras; +} + +/** + * cleanup ioctl extras + */ +void +netsnmp_ioctl_ipaddress_entry_cleanup(netsnmp_ipaddress_entry *entry) +{ + if (NULL == entry) { + netsnmp_assert(NULL != entry); + return; + } + + if (NULL == entry->arch_data) { + netsnmp_assert(NULL != entry->arch_data); + return; + } + + netsnmp_remove_list_node(&entry->arch_data, LIST_TOKEN); +} + +#ifndef NETSNMP_FEATURE_REMOVE_IPADDRESS_IOCTL_ENTRY_COPY +/** + * copy ioctl extras + * + * @retval 0: success + * @retval <0: error + */ +int +netsnmp_ioctl_ipaddress_entry_copy(netsnmp_ipaddress_entry *lhs, + netsnmp_ipaddress_entry *rhs) +{ + _ioctl_extras *lhs_extras, *rhs_extras; + int rc = SNMP_ERR_NOERROR; + + if ((NULL == lhs) || (NULL == rhs)) { + netsnmp_assert((NULL != lhs) && (NULL != rhs)); + return -1; + } + + rhs_extras = netsnmp_ioctl_ipaddress_extras_get(rhs); + lhs_extras = netsnmp_ioctl_ipaddress_extras_get(lhs); + if (NULL == rhs_extras) { + if (NULL != lhs_extras) + netsnmp_ioctl_ipaddress_entry_cleanup(lhs); + } + else { + if (NULL == lhs_extras) + lhs_extras = netsnmp_ioctl_ipaddress_entry_init(lhs); + + if (NULL != lhs_extras) + memcpy(lhs_extras, rhs_extras, sizeof(_ioctl_extras)); + else + rc = -1; + } + + return rc; +} +#endif /* NETSNMP_FEATURE_REMOVE_IPADDRESS_IOCTL_ENTRY_COPY */ + +/** + * load ipv4 address via ioctl + */ +int +_netsnmp_ioctl_ipaddress_container_load_v4(netsnmp_container *container, + int idx_offset) +{ + int i, sd, rc = 0, interfaces = 0; + struct ifconf ifc; + struct ifreq *ifrp; + struct sockaddr save_addr; + struct sockaddr_in * si; + struct address_flag_info addr_info; + in_addr_t ipval; + _ioctl_extras *extras; + + if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + snmp_log(LOG_ERR, "could not create socket\n"); + return -1; + } + + interfaces = + netsnmp_access_ipaddress_ioctl_get_interface_count(sd, &ifc); + if(interfaces < 0) { + close(sd); + return -2; + } + netsnmp_assert(NULL != ifc.ifc_buf); + DEBUGMSGTL(("access:ipaddress:container", "processing %d interfaces\n", interfaces)); + + ifrp = ifc.ifc_req; + for(i=0; i < interfaces; ++i, ++ifrp) { + netsnmp_ipaddress_entry *entry, *bcastentry = NULL; + + DEBUGMSGTL(("access:ipaddress:container", + " interface %d, %s\n", i, ifrp->ifr_name)); + + if (AF_INET != ifrp->ifr_addr.sa_family) { + DEBUGMSGTL(("access:ipaddress:container", + " skipping %s; non AF_INET family %d\n", + ifrp->ifr_name, ifrp->ifr_addr.sa_family)); + continue; + } + + /* + */ + entry = netsnmp_access_ipaddress_entry_create(); + if(NULL == entry) { + rc = -3; + break; + } + entry->ns_ia_index = ++idx_offset; + + /* + * save if name + */ + extras = netsnmp_ioctl_ipaddress_extras_get(entry); + memcpy(extras->name, ifrp->ifr_name, sizeof(extras->name)); + + /* + * each time we make an ioctl, we need to specify the address, but + * it will be overwritten in the call. so we save address here. + */ + save_addr = ifrp->ifr_addr; + + /* + * set indexes + */ + si = (struct sockaddr_in *) &ifrp->ifr_addr; + entry->ia_address_len = sizeof(si->sin_addr.s_addr); + ipval = si->sin_addr.s_addr; + memcpy(entry->ia_address, &si->sin_addr.s_addr, + entry->ia_address_len); + + /* + * get ifindex + */ + { + /* + * I think that Linux and Solaris both use ':' in the + * interface name for aliases. When a new arch is added + * that uses some other indicator, a new function, maybe + * netsnmp_access_ipaddress_entry_name_alias_check(), will + * need to be written. + */ + char *ptr = strchr(ifrp->ifr_name, ':'); + if (NULL != ptr) { + entry->flags |= NETSNMP_ACCESS_IPADDRESS_ISALIAS; + *ptr = 0; + } + } + entry->if_index = + netsnmp_access_interface_ioctl_ifindex_get(sd, ifrp->ifr_name); + if (0 == entry->if_index) { + snmp_log(LOG_ERR,"no ifindex found for interface\n"); + netsnmp_access_ipaddress_entry_free(entry); + continue; + } + + /* restore the interface name if we modifed it due to unaliasing + * above + */ + if (entry->flags & NETSNMP_ACCESS_IPADDRESS_ISALIAS) { + memcpy(ifrp->ifr_name, extras->name, sizeof(extras->name)); + } + + /* + * get broadcast + */ + memset(&addr_info, 0, sizeof(struct address_flag_info)); +#if defined (NETSNMP_ENABLE_IPV6) + addr_info = netsnmp_access_other_info_get(entry->if_index, AF_INET); + if(addr_info.bcastflg) { + bcastentry = netsnmp_access_ipaddress_entry_create(); + if(NULL == bcastentry) { + rc = -3; + break; + } + bcastentry->if_index = entry->if_index; + bcastentry->ns_ia_index = ++idx_offset; + bcastentry->ia_address_len = sizeof(addr_info.addr); + memcpy(bcastentry->ia_address, &addr_info.addr, + bcastentry->ia_address_len); + } +#endif + + /* + * get netmask + */ + ifrp->ifr_addr = save_addr; + if (ioctl(sd, SIOCGIFNETMASK, ifrp) < 0) { + snmp_log(LOG_ERR, + "error getting netmask for interface %d\n", i); + netsnmp_access_ipaddress_entry_free(entry); + continue; + } + netsnmp_assert(AF_INET == ifrp->ifr_addr.sa_family); + si = (struct sockaddr_in *) &ifrp->ifr_addr; + entry->ia_prefix_len = + netsnmp_ipaddress_ipv4_prefix_len(ntohl(si->sin_addr.s_addr)); + if(bcastentry) + bcastentry->ia_prefix_len = entry->ia_prefix_len; + + + /* + * get flags + */ + ifrp->ifr_addr = save_addr; + if (ioctl(sd, SIOCGIFFLAGS, ifrp) < 0) { + snmp_log(LOG_ERR, + "error getting if_flags for interface %d\n", i); + netsnmp_access_ipaddress_entry_free(entry); + continue; + } + extras->flags = ifrp->ifr_flags; + + if(bcastentry) + bcastentry->ia_type = IPADDRESSTYPE_BROADCAST; + if(addr_info.anycastflg) + entry->ia_type = IPADDRESSTYPE_ANYCAST; + else + entry->ia_type = IPADDRESSTYPE_UNICAST; + + /** entry->ia_prefix_oid ? */ + + /* + * per the MIB: + * In the absence of other information, an IPv4 address is + * always preferred(1). + */ + entry->ia_status = IPADDRESSSTATUSTC_PREFERRED; + if(bcastentry) + bcastentry->ia_status = IPADDRESSSTATUSTC_PREFERRED; + + /* + * can we figure out if an address is from DHCP? + * use manual until then... + */ + if(IS_APIPA(ipval)) { + entry->ia_origin = IPADDRESSORIGINTC_RANDOM; + if(bcastentry) + bcastentry->ia_origin = IPADDRESSORIGINTC_RANDOM; + } + else { + entry->ia_origin = IPADDRESSORIGINTC_MANUAL; + if(bcastentry) + bcastentry->ia_origin = IPADDRESSORIGINTC_MANUAL; + } + + DEBUGIF("access:ipaddress:container") { + DEBUGMSGT_NC(("access:ipaddress:container", + " if %d: addr len %d, index 0x%" NETSNMP_PRIo "x\n", + i, entry->ia_address_len, entry->if_index)); + if (4 == entry->ia_address_len) + DEBUGMSGT_NC(("access:ipaddress:container", " address %p\n", + *((void**)entry->ia_address))); + DEBUGMSGT_NC(("access:ipaddress:container", "flags 0x%x\n", + extras->flags)); + _print_flags(extras->flags); + + } + + /* + * add entry to container + */ + if(bcastentry){ + if (CONTAINER_INSERT(container, bcastentry) < 0) { + DEBUGMSGTL(("access:ipaddress:container","error with ipaddress_entry: insert broadcast entry into container failed.\n")); + netsnmp_access_ipaddress_entry_free(bcastentry); + netsnmp_access_ipaddress_entry_free(entry); + continue; + } + bcastentry = NULL; + } + + if (CONTAINER_INSERT(container, entry) < 0) { + DEBUGMSGTL(("access:ipaddress:container","error with ipaddress_entry: insert into container failed.\n")); + NETSNMP_LOGONCE((LOG_ERR, "Duplicate IPv4 address detected, some interfaces may not be visible in IP-MIB\n")); + netsnmp_access_ipaddress_entry_free(entry); + continue; + } + } + + /* + * clean up + */ + free(ifc.ifc_buf); + close(sd); + + /* + * return number of interfaces seen + */ + if(rc < 0) + return rc; + + return idx_offset; +} + +/** + * find unused alias number + */ +static int +_next_alias(const char *if_name) +{ + int i, j, k, sd, interfaces = 0, len; + struct ifconf ifc; + struct ifreq *ifrp; + char *alias; + int *alias_list; + + if (NULL == if_name) + return -1; + len = strlen(if_name); + + if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + snmp_log(LOG_ERR, "could not create socket\n"); + return -1; + } + + interfaces = + netsnmp_access_ipaddress_ioctl_get_interface_count(sd, &ifc); + if(interfaces < 0) { + close(sd); + return -2; + } + netsnmp_assert(NULL != ifc.ifc_buf); + DEBUGMSGTL(("access:ipaddress:container", "processing %d interfaces\n", interfaces)); + + alias_list = (int*)malloc(interfaces * sizeof(int)); + if (NULL == alias_list) { + close(sd); + return -2; + } + + ifrp = ifc.ifc_req; + for(i=0,j=0; i < interfaces; ++i, ++ifrp) { + + if (strncmp(ifrp->ifr_name, if_name, len) != 0) + continue; + + DEBUGMSGTL(("access:ipaddress:container", + " interface %d, %s\n", i, ifrp->ifr_name)); + + alias = strchr(ifrp->ifr_name, ':'); + if (NULL == alias) + continue; + + ++alias; /* skip ':' */ + alias_list[j++] = atoi(alias); + } + + /* + * clean up + */ + free(ifc.ifc_buf); + close(sd); + + /* + * return first unused alias + */ + for(i=1; i<=interfaces; ++i) { + for(k=0;k<j;++k) + if (alias_list[k] == i) + break; + if (k == j) { + free(alias_list); + return i; + } + } + + free(alias_list); + return interfaces + 1; +} + + +/** + * + * @retval 0 : no error + * @retval -1 : bad parameter + * @retval -2 : couldn't create socket + * @retval -3 : ioctl failed + */ +int +_netsnmp_ioctl_ipaddress_set_v4(netsnmp_ipaddress_entry * entry) +{ + struct ifreq ifrq; + struct sockaddr_in *sin; + int rc, fd = -1; + _ioctl_extras *extras; + + if (NULL == entry) + return -1; + + netsnmp_assert(4 == entry->ia_address_len); + + extras = netsnmp_ioctl_ipaddress_extras_get(entry); + if (NULL == extras) + return -1; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if(fd < 0) { + snmp_log(LOG_ERR,"couldn't create socket\n"); + return -2; + } + memset(&ifrq, 0, sizeof(ifrq)); + + if ('\0' == extras->name[0]) { + const char *name = netsnmp_access_interface_name_find(entry->if_index); + int alias_idx; + + if (NULL == name) { + DEBUGMSGT(("access:ipaddress:set", + "cant find name for index %" NETSNMP_PRIo "d\n", + entry->if_index)); + close(fd); + return -1; + } + + /* + * search for unused alias + */ + alias_idx = _next_alias(name); + snprintf(ifrq.ifr_name,sizeof(ifrq.ifr_name), "%s:%d", + name, alias_idx); + ifrq.ifr_name[sizeof(ifrq.ifr_name) - 1] = 0; + } + else + strlcpy(ifrq.ifr_name, (char *) extras->name, sizeof(ifrq.ifr_name)); + + sin = (struct sockaddr_in*)&ifrq.ifr_addr; + sin->sin_family = AF_INET; + memcpy(&sin->sin_addr.s_addr, entry->ia_address, + entry->ia_address_len); + + rc = ioctl(fd, SIOCSIFADDR, &ifrq); + close(fd); + if(rc < 0) { + snmp_log(LOG_ERR,"error setting address\n"); + return -3; + } + + return 0; +} + +/** + * + * @retval 0 : no error + * @retval -1 : bad parameter + * @retval -2 : couldn't create socket + * @retval -3 : ioctl failed + */ +int +_netsnmp_ioctl_ipaddress_delete_v4(netsnmp_ipaddress_entry * entry) +{ + struct ifreq ifrq; + int rc, fd = -1; + _ioctl_extras *extras; + + if (NULL == entry) + return -1; + + netsnmp_assert(4 == entry->ia_address_len); + + extras = netsnmp_ioctl_ipaddress_extras_get(entry); + if (NULL == extras) + return -1; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if(fd < 0) { + snmp_log(LOG_ERR,"couldn't create socket\n"); + return -2; + } + + memset(&ifrq, 0, sizeof(ifrq)); + + strlcpy(ifrq.ifr_name, (char *) extras->name, sizeof(ifrq.ifr_name)); + + ifrq.ifr_flags = 0; + + rc = ioctl(fd, SIOCSIFFLAGS, &ifrq); + close(fd); + if(rc < 0) { + snmp_log(LOG_ERR,"error deleting address\n"); + return -3; + } + + return 0; +} + + +/** + * Add/remove IPv6 address using ioctl. + * @retval 0 : no error + * @retval -1 : bad parameter + * @retval -2 : couldn't create socket + * @retval -3 : ioctl failed + */ +int +_netsnmp_ioctl_ipaddress_v6(netsnmp_ipaddress_entry * entry, int operation) +{ +#ifdef linux + /* + * From linux/ipv6.h. It cannot be included because it collides + * with netinet/in.h + */ + struct in6_ifreq { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + int ifr6_ifindex; + }; + + struct in6_ifreq ifrq; + int rc, fd = -1; + + DEBUGMSGT(("access:ipaddress:set", "_netsnmp_ioctl_ipaddress_set_v6 started\n")); + + if (NULL == entry) + return -1; + + netsnmp_assert(16 == entry->ia_address_len); + + fd = socket(AF_INET6, SOCK_DGRAM, 0); + if(fd < 0) { + snmp_log(LOG_ERR,"couldn't create socket\n"); + return -2; + } + memset(&ifrq, 0, sizeof(ifrq)); + ifrq.ifr6_ifindex = entry->if_index; + ifrq.ifr6_prefixlen = 64; + + memcpy(&ifrq.ifr6_addr, entry->ia_address, + entry->ia_address_len); + + rc = ioctl(fd, operation, &ifrq); + close(fd); + if(rc < 0) { + snmp_log(LOG_ERR,"error setting address: %s(%d)\n", strerror(errno), errno); + return -3; + } + DEBUGMSGT(("access:ipaddress:set", "_netsnmp_ioctl_ipaddress_set_v6 finished\n")); + return 0; +#else + /* we don't support ipv6 on this platform (yet) */ + return -3; +#endif + +} + +/** + * + * @retval 0 : no error + * @retval -1 : bad parameter + * @retval -2 : couldn't create socket + * @retval -3 : ioctl failed + */ +int +_netsnmp_ioctl_ipaddress_set_v6(netsnmp_ipaddress_entry * entry) +{ + return _netsnmp_ioctl_ipaddress_v6(entry, SIOCSIFADDR); +} + +/** + * + * @retval 0 : no error + * @retval -1 : bad parameter + * @retval -2 : couldn't create socket + * @retval -3 : ioctl failed + */ +int +_netsnmp_ioctl_ipaddress_delete_v6(netsnmp_ipaddress_entry * entry) +{ + return _netsnmp_ioctl_ipaddress_v6(entry, SIOCDIFADDR); +} + +/** + * get the interface count and populate the ifc_buf + * + * Note: the caller assumes responsbility for the ifc->ifc_buf + * memory, and should free() it when done. + * + * @retval -1 : malloc error + */ +int +netsnmp_access_ipaddress_ioctl_get_interface_count(int sd, struct ifconf * ifc) +{ + int lastlen = 0, i; + struct ifconf ifc_tmp; + + if (NULL == ifc) { + memset(&ifc_tmp, 0x0, sizeof(ifc_tmp)); + ifc = &ifc_tmp; + } + + /* + * Cope with lots of interfaces and brokenness of ioctl SIOCGIFCONF + * on some platforms; see W. R. Stevens, ``Unix Network Programming + * Volume I'', p.435. + */ + + for (i = 8;; i *= 2) { + ifc->ifc_buf = (caddr_t)calloc(i, sizeof(struct ifreq)); + if (NULL == ifc->ifc_buf) { + snmp_log(LOG_ERR, "could not allocate memory for %d interfaces\n", + i); + return -1; + } + ifc->ifc_len = i * sizeof(struct ifreq); + + if (ioctl(sd, SIOCGIFCONF, (char *) ifc) < 0) { + if (errno != EINVAL || lastlen != 0) { + /* + * Something has gone genuinely wrong. + */ + snmp_log(LOG_ERR, "bad rc from ioctl, errno %d", errno); + SNMP_FREE(ifc->ifc_buf); + return -1; + } + /* + * Otherwise, it could just be that the buffer is too small. + */ + } else { + if (ifc->ifc_len == lastlen) { + /* + * The length is the same as the last time; we're done. + */ + break; + } + lastlen = ifc->ifc_len; + } + free(ifc->ifc_buf); /* no SNMP_FREE, getting ready to reassign */ + } + + if (ifc == &ifc_tmp) + free(ifc_tmp.ifc_buf); + + return ifc->ifc_len / sizeof(struct ifreq); +} + +/** + */ +static void +_print_flags(short flags) +{ +/** Standard interface flags. */ + struct { + short flag; + const char *name; + } map[] = { + { IFF_UP, "interface is up"}, + { IFF_BROADCAST, "broadcast address valid"}, + { IFF_DEBUG, "turn on debugging"}, + { IFF_LOOPBACK, "is a loopback net"}, + { IFF_POINTOPOINT, "interface is has p-p link"}, + { IFF_NOTRAILERS, "avoid use of trailers"}, + { IFF_RUNNING, "resources allocated"}, + { IFF_NOARP, "no ARP protocol"}, + { IFF_PROMISC, "receive all packets"}, + { IFF_ALLMULTI, "receive all multicast packets"}, + { IFF_MASTER, "master of a load balancer"}, + { IFF_SLAVE, "slave of a load balancer"}, + { IFF_MULTICAST, "Supports multicast"}, + { IFF_PORTSEL, "can set media type"}, + { IFF_AUTOMEDIA, "auto media select active"}, + }; + short unknown = flags; + size_t i; + + for(i = 0; i < sizeof(map)/sizeof(map[0]); ++i) + if(flags & map[i].flag) { + DEBUGMSGT_NC(("access:ipaddress:container"," %s\n", map[i].name)); + unknown &= ~map[i].flag; + } + + if(unknown) + DEBUGMSGT_NC(("access:ipaddress:container"," unknown 0x%x\n", unknown)); +} |