From: mikeb Date: Mon, 28 Apr 2014 12:03:32 +0000 (+0000) Subject: Add support for exporting ARP table via ipNetToMediaTable OID. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=49dc4f7fac48d95b89b7dd32b0c8e66418349fa6;p=openbsd Add support for exporting ARP table via ipNetToMediaTable OID. With help from blambert@ and sthen@, tested by sthen@, benno@ and myself; ok blambert --- diff --git a/usr.sbin/snmpd/kroute.c b/usr.sbin/snmpd/kroute.c index 1ed4d171883..166b7159ca9 100644 --- a/usr.sbin/snmpd/kroute.c +++ b/usr.sbin/snmpd/kroute.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kroute.c,v 1.24 2013/10/30 17:24:35 deraadt Exp $ */ +/* $OpenBSD: kroute.c,v 1.25 2014/04/28 12:03:32 mikeb Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter @@ -71,6 +71,7 @@ struct kroute6_node { struct kif_node { RB_ENTRY(kif_node) entry; TAILQ_HEAD(, kif_addr) addrs; + TAILQ_HEAD(, kif_arp) arps; struct kif k; }; @@ -93,6 +94,10 @@ int kroute6_insert(struct kroute6_node *); int kroute6_remove(struct kroute6_node *); void kroute6_clear(void); +struct kif_arp *karp_find(struct sockaddr *, u_short); +int karp_insert(struct kif_node *, struct kif_arp *); +int karp_remove(struct kif_node *, struct kif_arp *); + struct kif_node *kif_find(u_short); struct kif_node *kif_insert(u_short); int kif_remove(struct kif_node *); @@ -120,6 +125,7 @@ void if_announce(void *); int fetchtable(void); int fetchifs(u_short); +int fetcharp(void); void dispatch_rtmsg(int, short, void *); int rtmsg_process(char *, int); int dispatch_rtmsg_addr(struct rt_msghdr *, @@ -184,6 +190,8 @@ kr_init(void) fatalx("kr_init fetchifs"); if (fetchtable() == -1) fatalx("kr_init fetchtable"); + if (fetcharp() == -1) + fatalx("kr_init fetcharp"); event_set(&kr_state.ks_ev, kr_state.ks_fd, EV_READ | EV_PERSIST, dispatch_rtmsg, NULL); @@ -521,6 +529,119 @@ kroute6_clear(void) kroute6_remove(kr); } +static inline int +karp_compare(struct kif_arp *a, struct kif_arp *b) +{ + /* Interface indices are assumed equal */ + if (ntohl(a->addr.sin.sin_addr.s_addr) > + ntohl(b->addr.sin.sin_addr.s_addr)) + return (1); + if (ntohl(a->addr.sin.sin_addr.s_addr) < + ntohl(b->addr.sin.sin_addr.s_addr)) + return (-1); + return (0); +} + +static inline struct kif_arp * +karp_search(struct kif_node *kn, struct kif_arp *ka) +{ + struct kif_arp *pivot; + + TAILQ_FOREACH(pivot, &kn->arps, entry) { + switch (karp_compare(ka, pivot)) { + case 0: /* found */ + return (pivot); + case -1: /* ka < pivot, end the search */ + return (NULL); + } + } + /* looped through the whole list and didn't find */ + return (NULL); +} + +struct kif_arp * +karp_find(struct sockaddr *sa, u_short ifindex) +{ + struct kif_node *kn; + struct kif_arp *ka = NULL, s; + + memcpy(&s.addr.sa, sa, sa->sa_len); + + if (ifindex == 0) { + /* + * We iterate manually to handle zero ifindex special + * case differently from kif_find, in particular we + * want to look for the address on all available + * interfaces. + */ + RB_FOREACH(kn, kif_tree, &kit) { + if ((ka = karp_search(kn, &s)) != NULL) + break; + } + } else { + if ((kn = kif_find(ifindex)) == NULL) + return (NULL); + ka = karp_search(kn, &s); + } + return (ka); +} + +int +karp_insert(struct kif_node *kn, struct kif_arp *ka) +{ + struct kif_arp *pivot; + + if (ka->if_index == 0) + return (-1); + if (!kn && (kn = kif_find(ka->if_index)) == NULL) + return (-1); + /* Put entry on the list in the ascending lexical order */ + TAILQ_FOREACH(pivot, &kn->arps, entry) { + switch (karp_compare(ka, pivot)) { + case 0: /* collision */ + return (-1); + case -1: /* ka < pivot */ + TAILQ_INSERT_BEFORE(pivot, ka, entry); + return (0); + } + } + /* ka is larger than any other element on the list */ + TAILQ_INSERT_TAIL(&kn->arps, ka, entry); + return (0); +} + +int +karp_remove(struct kif_node *kn, struct kif_arp *ka) +{ + if (ka->if_index == 0) + return (-1); + if (!kn && (kn = kif_find(ka->if_index)) == NULL) + return (-1); + TAILQ_REMOVE(&kn->arps, ka, entry); + free(ka); + return (0); +} + +struct kif_arp * +karp_first(u_short ifindex) +{ + struct kif_node *kn; + + if ((kn = kif_find(ifindex)) == NULL) + return (NULL); + return (TAILQ_FIRST(&kn->arps)); +} + +struct kif_arp * +karp_getaddr(struct sockaddr *sa, u_short ifindex, int next) +{ + struct kif_arp *ka; + + if ((ka = karp_find(sa, ifindex)) == NULL) + return (NULL); + return (next ? TAILQ_NEXT(ka, entry) : ka); +} + struct kif_node * kif_find(u_short if_index) { @@ -572,6 +693,7 @@ kif_insert(u_short if_index) kif->k.if_index = if_index; TAILQ_INIT(&kif->addrs); + TAILQ_INIT(&kif->arps); if (RB_INSERT(kif_tree, &kit, kif) != NULL) fatalx("kif_insert: RB_INSERT"); @@ -586,6 +708,7 @@ int kif_remove(struct kif_node *kif) { struct kif_addr *ka; + struct kif_arp *kr; if (RB_REMOVE(kif_tree, &kit, kif) == NULL) { log_warnx("RB_REMOVE(kif_tree, &kit, kif)"); @@ -596,6 +719,9 @@ kif_remove(struct kif_node *kif) TAILQ_REMOVE(&kif->addrs, ka, entry); ka_remove(ka); } + while ((kr = TAILQ_FIRST(&kif->arps)) != NULL) { + karp_remove(kif, kr); + } free(kif); kr_state.ks_nkif--; @@ -897,6 +1023,8 @@ if_announce(void *msg) kif = kif_insert(ifan->ifan_index); strlcpy(kif->k.if_name, ifan->ifan_name, sizeof(kif->k.if_name)); + /* Update the ARP table */ + fetcharp(); break; case IFAN_DEPARTURE: kif = kif_find(ifan->ifan_index); @@ -976,6 +1104,45 @@ fetchifs(u_short if_index) return (rv); } +int +fetcharp(void) +{ + size_t len; + int mib[7]; + char *buf; + int rv; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET; + mib[4] = NET_RT_FLAGS; + mib[5] = RTF_LLINFO; + mib[6] = 0; + + if (sysctl(mib, 7, NULL, &len, NULL, 0) == -1) { + log_warn("sysctl"); + return (-1); + } + /* Empty table? */ + if (len == 0) + return (0); + if ((buf = malloc(len)) == NULL) { + log_warn("fetcharp"); + return (-1); + } + if (sysctl(mib, 7, buf, &len, NULL, 0) == -1) { + log_warn("sysctl"); + free(buf); + return (-1); + } + + rv = rtmsg_process(buf, len); + free(buf); + + return (rv); +} + /* ARGSUSED */ void dispatch_rtmsg(int fd, short event, void *arg) @@ -1020,10 +1187,9 @@ rtmsg_process(char *buf, int len) case RTM_GET: case RTM_CHANGE: case RTM_DELETE: + case RTM_RESOLVE: if (rtm->rtm_errno) /* failed attempts */ continue; - if (rtm->rtm_flags & RTF_LLINFO) /* arp cache */ - continue; if (dispatch_rtmsg_addr(rtm, rti_info) == -1) return (-1); @@ -1069,8 +1235,10 @@ dispatch_rtmsg_addr(struct rt_msghdr *rtm, struct sockaddr *rti_info[RTAX_MAX]) struct sockaddr *sa, *psa; struct sockaddr_in *sa_in, *psa_in = NULL; struct sockaddr_in6 *sa_in6, *psa_in6 = NULL; + struct sockaddr_dl *sa_dl; struct kroute_node *kr; struct kroute6_node *kr6; + struct kif_arp *ka; int flags, mpath = 0; u_int16_t ifindex; u_int8_t prefixlen; @@ -1131,12 +1299,25 @@ dispatch_rtmsg_addr(struct rt_msghdr *rtm, struct sockaddr *rti_info[RTAX_MAX]) case AF_LINK: flags |= F_CONNECTED; ifindex = rtm->rtm_index; - sa = NULL; mpath = 0; /* link local stuff can't be mpath */ break; } if (rtm->rtm_type == RTM_DELETE) { + if (sa != NULL && sa->sa_family == AF_LINK && + (rtm->rtm_flags & RTF_HOST) && + psa->sa_family == AF_INET) { + if ((ka = karp_find(psa, ifindex)) == NULL) + return (0); + if (karp_remove(NULL, ka) == -1) + return (-1); + return (0); + } else if (sa == NULL && (rtm->rtm_flags & RTF_HOST) && + psa->sa_family == AF_INET) { + if ((ka = karp_find(psa, ifindex)) != NULL) + karp_remove(NULL, ka); + /* Continue to the route section below */ + } switch (psa->sa_family) { case AF_INET: sa_in = (struct sockaddr_in *)sa; @@ -1180,6 +1361,42 @@ dispatch_rtmsg_addr(struct rt_msghdr *rtm, struct sockaddr *rti_info[RTAX_MAX]) if (sa == NULL && !(flags & F_CONNECTED)) return (0); + /* Add or update an ARP entry */ + if ((rtm->rtm_flags & RTF_LLINFO) && (rtm->rtm_flags & RTF_HOST) && + sa != NULL && sa->sa_family == AF_LINK && + psa->sa_family == AF_INET) { + sa_dl = (struct sockaddr_dl *)sa; + /* ignore incomplete entries */ + if (!sa_dl->sdl_alen) + return (0); + /* ignore entries that do not specify an interface */ + if (ifindex == 0) + return (0); + if ((ka = karp_find(psa, ifindex)) != NULL) { + memcpy(&ka->target.sdl, sa_dl, sa_dl->sdl_len); + if (rtm->rtm_flags & RTF_PERMANENT_ARP) + flags |= F_STATIC; + ka->flags = flags; + } else { + if ((ka = calloc(1, sizeof(struct kif_arp))) == NULL) { + log_warn("dispatch_rtmsg"); + return (-1); + } + memcpy(&ka->addr.sa, psa, psa->sa_len); + memcpy(&ka->target.sdl, sa_dl, sa_dl->sdl_len); + if (rtm->rtm_flags & RTF_PERMANENT_ARP) + flags |= F_STATIC; + ka->flags = flags; + ka->if_index = ifindex; + if (karp_insert(NULL, ka)) { + free(ka); + log_warnx("dispatch_rtmsg: failed to insert"); + return (-1); + } + } + return (0); + } + switch (psa->sa_family) { case AF_INET: sa_in = (struct sockaddr_in *)sa; diff --git a/usr.sbin/snmpd/mib.c b/usr.sbin/snmpd/mib.c index 03387c9f2d7..1893e10fa2c 100644 --- a/usr.sbin/snmpd/mib.c +++ b/usr.sbin/snmpd/mib.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mib.c,v 1.67 2014/04/08 14:04:11 mpi Exp $ */ +/* $OpenBSD: mib.c,v 1.68 2014/04/28 12:03:32 mikeb Exp $ */ /* * Copyright (c) 2012 Joel Knight @@ -2925,6 +2925,9 @@ int mib_iproutingdiscards(struct oid *, struct ber_oid *, int mib_ipaddr(struct oid *, struct ber_oid *, struct ber_element **); struct ber_oid * mib_ipaddrtable(struct oid *, struct ber_oid *, struct ber_oid *); +int mib_physaddr(struct oid *, struct ber_oid *, struct ber_element **); +struct ber_oid * + mib_physaddrtable(struct oid *, struct ber_oid *, struct ber_oid *); static struct oid ip_mib[] = { { MIB(ipMIB), OID_MIB }, @@ -2960,11 +2963,15 @@ static struct oid ip_mib[] = { mib_ipaddrtable }, { MIB(ipAdEntReasmMaxSize), OID_TRD, mib_ipaddr, NULL, mib_ipaddrtable }, + { MIB(ipNetToMediaIfIndex), OID_TRD, mib_physaddr, NULL, + mib_physaddrtable }, + { MIB(ipNetToMediaPhysAddress), OID_TRD, mib_physaddr, NULL, + mib_physaddrtable }, + { MIB(ipNetToMediaNetAddress), OID_TRD, mib_physaddr, NULL, + mib_physaddrtable }, + { MIB(ipNetToMediaType), OID_TRD, mib_physaddr, NULL, + mib_physaddrtable }, #ifdef notyet - { MIB(ipNetToMediaIfIndex) }, - { MIB(ipNetToMediaPhysAddress) }, - { MIB(ipNetToMediaNetAddress) }, - { MIB(ipNetToMediaType) }, { MIB(ipRoutingDiscards) }, #endif { MIBEND } @@ -3253,6 +3260,149 @@ mib_ipaddr(struct oid *oid, struct ber_oid *o, struct ber_element **elm) return (0); } +struct ber_oid * +mib_physaddrtable(struct oid *oid, struct ber_oid *o, struct ber_oid *no) +{ + struct sockaddr_in addr; + struct oid a, b; + struct kif *kif; + struct kif_arp *ka; + u_int32_t id, idx = 0; + + bcopy(&oid->o_id, no, sizeof(*no)); + id = oid->o_oidlen - 1; + + if (o->bo_n >= oid->o_oidlen) { + /* + * Compare the requested and the matched OID to see + * if we have to iterate to the next element. + */ + bzero(&a, sizeof(a)); + bcopy(o, &a.o_id, sizeof(struct ber_oid)); + bzero(&b, sizeof(b)); + bcopy(&oid->o_id, &b.o_id, sizeof(struct ber_oid)); + b.o_oidlen--; + b.o_flags |= OID_TABLE; + if (smi_oid_cmp(&a, &b) == 0) { + o->bo_id[id] = oid->o_oid[id]; + bcopy(o, no, sizeof(*no)); + } + } + + if (o->bo_n > OIDIDX_ipNetToMedia + 1) + idx = o->bo_id[OIDIDX_ipNetToMedia + 1]; + + bzero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_len = sizeof(addr); + if (o->bo_n > OIDIDX_ipNetToMedia + 2) + mps_decodeinaddr(no, &addr.sin_addr, OIDIDX_ipNetToMedia + 2); + + if ((kif = kr_getif(idx)) == NULL) { + /* No configured interfaces */ + if (idx == 0) + return (NULL); + /* + * It may happen that an interface with a specific index + * does not exist or has been removed. Jump to the next + * available interface. + */ + kif = kr_getif(0); + nextif: + for (; kif != NULL; kif = kr_getnextif(kif->if_index)) + if (kif->if_index > idx && + (ka = karp_first(kif->if_index)) != NULL) + break; + if (kif == NULL) { + /* No more interfaces with addresses on them */ + o->bo_id[OIDIDX_ipNetToMedia + 1] = 0; + mps_encodeinaddr(no, NULL, OIDIDX_ipNetToMedia + 2); + smi_oidlen(o); + return (NULL); + } + } else { + if (idx == 0 || addr.sin_addr.s_addr == 0) + ka = karp_first(kif->if_index); + else + ka = karp_getaddr((struct sockaddr *)&addr, idx, 1); + if (ka == NULL) { + /* Try next interface */ + goto nextif; + } + } + idx = kif->if_index; + + no->bo_id[OIDIDX_ipNetToMedia + 1] = idx; + /* Encode real IPv4 address */ + memcpy(&addr, &ka->addr.sin, ka->addr.sin.sin_len); + mps_encodeinaddr(no, &addr.sin_addr, OIDIDX_ipNetToMedia + 2); + + smi_oidlen(o); + return (no); +} + +int +mib_physaddr(struct oid *oid, struct ber_oid *o, struct ber_element **elm) +{ + struct ber_element *ber = *elm; + struct sockaddr_in addr; + struct kif_arp *ka; + u_int32_t val, idx = 0; + + idx = o->bo_id[OIDIDX_ipNetToMedia + 1]; + if (idx == 0) { + /* Strip invalid interface index and fail */ + o->bo_n = OIDIDX_ipNetToMedia + 1; + return (1); + } + + /* Get the IP address */ + bzero(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_len = sizeof(addr); + + if (mps_decodeinaddr(o, &addr.sin_addr, + OIDIDX_ipNetToMedia + 2) == -1) { + /* Strip invalid address and fail */ + o->bo_n = OIDIDX_ipNetToMedia + 2; + return (1); + } + if ((ka = karp_getaddr((struct sockaddr *)&addr, idx, 0)) == NULL) + return (1); + + /* write OID */ + ber = ber_add_oid(ber, o); + + switch (o->bo_id[OIDIDX_ipNetToMedia]) { + case 1: /* ipNetToMediaIfIndex */ + ber = ber_add_integer(ber, ka->if_index); + break; + case 2: /* ipNetToMediaPhysAddress */ + if (bcmp(LLADDR(&ka->target.sdl), ether_zeroaddr, + sizeof(ether_zeroaddr)) == 0) + ber = ber_add_nstring(ber, ether_zeroaddr, + sizeof(ether_zeroaddr)); + else + ber = ber_add_nstring(ber, LLADDR(&ka->target.sdl), + ka->target.sdl.sdl_alen); + break; + case 3: /* ipNetToMediaNetAddress */ + val = addr.sin_addr.s_addr; + ber = ber_add_nstring(ber, (char *)&val, sizeof(u_int32_t)); + ber_set_header(ber, BER_CLASS_APPLICATION, SNMP_T_IPADDR); + break; + case 4: /* ipNetToMediaType */ + if (ka->flags & F_STATIC) + ber = ber_add_integer(ber, 4); /* static */ + else + ber = ber_add_integer(ber, 3); /* dynamic */ + break; + default: + return (-1); + } + return (0); +} + /* * Defined in IP-FORWARD-MIB.txt (rfc4292) */ diff --git a/usr.sbin/snmpd/mib.h b/usr.sbin/snmpd/mib.h index 2551adbba6e..41af695b6f2 100644 --- a/usr.sbin/snmpd/mib.h +++ b/usr.sbin/snmpd/mib.h @@ -1,4 +1,4 @@ -/* $OpenBSD: mib.h,v 1.33 2013/10/01 19:24:46 reyk Exp $ */ +/* $OpenBSD: mib.h,v 1.34 2014/04/28 12:03:32 mikeb Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter @@ -326,6 +326,7 @@ #define MIB_ipAdEntReasmMaxSize MIB_ipAddrEntry, 5 #define MIB_ipNetToMediaTable MIB_ipMIB, 22 #define MIB_ipNetToMediaEntry MIB_ipNetToMediaTable, 1 +#define OIDIDX_ipNetToMedia 9 #define MIB_ipNetToMediaIfIndex MIB_ipNetToMediaEntry, 1 #define MIB_ipNetToMediaPhysAddress MIB_ipNetToMediaEntry, 2 #define MIB_ipNetToMediaNetAddress MIB_ipNetToMediaEntry, 3 @@ -1284,7 +1285,6 @@ { MIBDECL(ipNetToMediaIfIndex) }, \ { MIBDECL(ipNetToMediaPhysAddress) }, \ { MIBDECL(ipNetToMediaNetAddress) }, \ - { MIBDECL(ipNetToMediaType) }, \ { MIBDECL(ipNetToMediaType) }, \ \ { MIBDECL(ipfMIB) }, \ diff --git a/usr.sbin/snmpd/snmpd.h b/usr.sbin/snmpd/snmpd.h index 37cb603e270..39572df2b24 100644 --- a/usr.sbin/snmpd/snmpd.h +++ b/usr.sbin/snmpd/snmpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpd.h,v 1.53 2014/04/28 08:25:05 blambert Exp $ */ +/* $OpenBSD: snmpd.h,v 1.54 2014/04/28 12:03:32 mikeb Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -191,6 +192,7 @@ union kaddr { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; + struct sockaddr_dl sdl; char pad[32]; }; @@ -224,6 +226,15 @@ struct kif_addr { RB_ENTRY(kif_addr) node; }; +struct kif_arp { + u_short flags; + u_short if_index; + union kaddr addr; + union kaddr target; + + TAILQ_ENTRY(kif_arp) entry; +}; + struct kif { char if_name[IF_NAMESIZE]; char if_descr[IFDESCRSIZE]; @@ -578,6 +589,9 @@ struct kif_addr *kr_getnextaddr(struct sockaddr *); struct kroute *kroute_first(void); struct kroute *kroute_getaddr(in_addr_t, u_int8_t, u_int8_t, int); +struct kif_arp *karp_first(u_short); +struct kif_arp *karp_getaddr(struct sockaddr *, u_short, int); + /* snmpe.c */ pid_t snmpe(struct privsep *, struct privsep_proc *); void snmpe_shutdown(void);