SNMP uses UDP and snmpd listens on a single address. This means that you can have issues with the source address of the replies sent by snmpd. "listen on $loopback_address":
http://marc.info/?l=openbsd-misc&m=147445870822415&w=2 seems to be unsufficient, but I don't understand why it would fail. The diff below should fix the source address selection for replies. Configuring the source address for traps is a different problem and will be implemented in a different diff. WIP, only tested on a single box so far. Thoughts / oks? Index: snmpd.h =================================================================== RCS file: /cvs/src/usr.sbin/snmpd/snmpd.h,v retrieving revision 1.68 diff -u -p -r1.68 snmpd.h --- snmpd.h 25 Sep 2016 14:58:00 -0000 1.68 +++ snmpd.h 28 Sep 2016 20:11:05 -0000 @@ -390,6 +390,9 @@ struct snmp_message { socklen_t sm_slen; char sm_host[HOST_NAME_MAX+1]; + struct sockaddr_storage sm_local_ss; + socklen_t sm_local_slen; + struct ber sm_ber; struct ber_element *sm_req; struct ber_element *sm_resp; @@ -766,6 +769,10 @@ struct trapcmd * /* util.c */ int varbind_convert(struct agentx_pdu *, struct agentx_varbind_hdr *, struct ber_element **, struct ber_element **); +ssize_t sendtofrom(int, void *, size_t, int, struct sockaddr *, + socklen_t, struct sockaddr *, socklen_t); +ssize_t recvfromto(int, void *, size_t, int, struct sockaddr *, + socklen_t *, struct sockaddr *, socklen_t *); void print_debug(const char *, ...); void print_verbose(const char *, ...); const char *log_in6addr(const struct in6_addr *); Index: snmpe.c =================================================================== RCS file: /cvs/src/usr.sbin/snmpd/snmpe.c,v retrieving revision 1.42 diff -u -p -r1.42 snmpe.c --- snmpe.c 16 Aug 2016 18:41:57 -0000 1.42 +++ snmpe.c 28 Sep 2016 20:11:05 -0000 @@ -120,7 +120,7 @@ int snmpe_bind(struct address *addr) { char buf[512]; - int s; + int val, s; if ((s = snmpd_socket_af(&addr->ss, htons(addr->port))) == -1) return (-1); @@ -131,6 +131,26 @@ snmpe_bind(struct address *addr) if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) goto bad; + switch (addr->ss.ss_family) { + case AF_INET: + val = 1; + if (setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, + &val, sizeof(int)) == -1) { + log_warn("%s: failed to set IPv4 packet info", + __func__); + goto bad; + } + break; + case AF_INET6: + val = 1; + if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &val, sizeof(int)) == -1) { + log_warn("%s: failed to set IPv6 packet info", + __func__); + goto bad; + } + } + if (bind(s, (struct sockaddr *)&addr->ss, addr->ss.ss_len) == -1) goto bad; @@ -460,8 +480,9 @@ snmpe_recvmsg(int fd, short sig, void *a return; msg->sm_slen = sizeof(msg->sm_ss); - if ((len = recvfrom(fd, msg->sm_data, sizeof(msg->sm_data), 0, - (struct sockaddr *)&msg->sm_ss, &msg->sm_slen)) < 1) { + if ((len = recvfromto(fd, msg->sm_data, sizeof(msg->sm_data), 0, + (struct sockaddr *)&msg->sm_ss, &msg->sm_slen, + (struct sockaddr *)&msg->sm_local_ss, &msg->sm_local_slen)) < 1) { free(msg); return; } @@ -549,8 +570,9 @@ snmpe_response(int fd, struct snmp_messa goto done; usm_finalize_digest(msg, ptr, len); - len = sendto(fd, ptr, len, 0, (struct sockaddr *)&msg->sm_ss, - msg->sm_slen); + len = sendtofrom(fd, ptr, len, 0, + (struct sockaddr *)&msg->sm_ss, msg->sm_slen, + (struct sockaddr *)&msg->sm_local_ss, msg->sm_local_slen); if (len != -1) stats->snmp_outpkts++; Index: util.c =================================================================== RCS file: /cvs/src/usr.sbin/snmpd/util.c,v retrieving revision 1.5 diff -u -p -r1.5 util.c --- util.c 21 Nov 2015 13:06:22 -0000 1.5 +++ util.c 28 Sep 2016 20:11:05 -0000 @@ -156,6 +156,128 @@ varbind_convert(struct agentx_pdu *pdu, return (ret); } +ssize_t +sendtofrom(int s, void *buf, size_t len, int flags, struct sockaddr *to, + socklen_t tolen, struct sockaddr *from, socklen_t fromlen) +{ + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + struct in6_pktinfo *pkt6; + struct sockaddr_in *in; + struct sockaddr_in6 *in6; + union { + struct cmsghdr hdr; + char inbuf[CMSG_SPACE(sizeof(struct in_addr))]; + char in6buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf; + + bzero(&msg, sizeof(msg)); + bzero(&cmsgbuf, sizeof(cmsgbuf)); + + iov.iov_base = buf; + iov.iov_len = len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = to; + msg.msg_namelen = tolen; + msg.msg_control = &cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + cmsg = CMSG_FIRSTHDR(&msg); + switch (to->sa_family) { + case AF_INET: + msg.msg_controllen = sizeof(cmsgbuf.inbuf); + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_SENDSRCADDR; + in = (struct sockaddr_in *)from; + memcpy(CMSG_DATA(cmsg), &in->sin_addr, sizeof(struct in_addr)); + break; + case AF_INET6: + msg.msg_controllen = sizeof(cmsgbuf.in6buf); + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + in6 = (struct sockaddr_in6 *)from; + pkt6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); + pkt6->ipi6_addr = in6->sin6_addr; + break; + } + + return sendmsg(s, &msg, flags); +} + +ssize_t +recvfromto(int s, void *buf, size_t len, int flags, struct sockaddr *from, + socklen_t *fromlen, struct sockaddr *to, socklen_t *tolen) +{ + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + struct in6_pktinfo *pkt6; + struct sockaddr_in *in; + struct sockaddr_in6 *in6; + ssize_t ret; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(struct sockaddr_storage))]; + } cmsgbuf; + + bzero(&msg, sizeof(msg)); + bzero(&cmsgbuf.buf, sizeof(cmsgbuf.buf)); + + iov.iov_base = buf; + iov.iov_len = len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = from; + msg.msg_namelen = *fromlen; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + if ((ret = recvmsg(s, &msg, flags)) == -1) + return (-1); + + *fromlen = from->sa_len; + *tolen = 0; + + if (getsockname(s, to, tolen) != 0) + *tolen = 0; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + switch (from->sa_family) { + case AF_INET: + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_RECVDSTADDR) { + in = (struct sockaddr_in *)to; + in->sin_family = AF_INET; + in->sin_len = *tolen = sizeof(*in); + memcpy(&in->sin_addr, CMSG_DATA(cmsg), + sizeof(struct in_addr)); + } + break; + case AF_INET6: + if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_PKTINFO) { + in6 = (struct sockaddr_in6 *)to; + in6->sin6_family = AF_INET6; + in6->sin6_len = *tolen = sizeof(*in6); + pkt6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); + memcpy(&in6->sin6_addr, &pkt6->ipi6_addr, + sizeof(struct in6_addr)); + if (IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr)) + in6->sin6_scope_id = + pkt6->ipi6_ifindex; + } + break; + } + } + + return (ret); +} + void print_debug(const char *emsg, ...) { -- jca | PGP : 0x1524E7EE / 5135 92C1 AD36 5293 2BDF DDCC 0DFA 74AE 1524 E7EE