Jeremie Courreges-Anglas <j...@wxcvbn.org> writes: > j...@wxcvbn.org (Jeremie Courreges-Anglas) writes: > >> 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. > > Updated diff that implements a "source-address" parameter for trap > receivers, as discussed with Reyk. > >> WIP, only tested on a single box so far. Thoughts / oks? > > source-address lightly tested with: > trap receiver 127.0.0.1 > trap receiver 127.0.0.1 source-address 192.168.0.44 > trap receiver fe80::1%lo0 source-address ::1 > > Comments and test reports welcome.
Notes regarding source-address: - only try to handle an address, not a hostname. I didn't want to handle the additional complexity as I'm not sure it's worth it. - say we have "trap receiver somehostname source-address fd00::1". Should we try to filter out potential IPv4 addresses and find an appropriate IPv6 address? The diff below would error out if the first record found is an IPv4. > > Index: parse.y > =================================================================== > RCS file: /cvs/src/usr.sbin/snmpd/parse.y,v > retrieving revision 1.39 > diff -u -p -r1.39 parse.y > --- parse.y 21 Jun 2016 21:35:25 -0000 1.39 > +++ parse.y 30 Sep 2016 13:33:28 -0000 > @@ -98,9 +98,10 @@ static int nctlsocks = 0; > struct address *host_v4(const char *); > struct address *host_v6(const char *); > int host_dns(const char *, struct addresslist *, > - int, in_port_t, struct ber_oid *, char *); > + int, in_port_t, struct ber_oid *, char *, > + struct address *); > int host(const char *, struct addresslist *, > - int, in_port_t, struct ber_oid *, char *); > + int, in_port_t, struct ber_oid *, char *, char *); > > typedef struct { > union { > @@ -127,10 +128,11 @@ typedef struct { > %token SYSTEM CONTACT DESCR LOCATION NAME OBJECTID SERVICES RTFILTER > %token READONLY READWRITE OCTETSTRING INTEGER COMMUNITY TRAP RECEIVER > %token SECLEVEL NONE AUTH ENC USER AUTHKEY ENCKEY ERROR DISABLED > -%token SOCKET RESTRICTED AGENTX HANDLE DEFAULT > +%token SOCKET RESTRICTED AGENTX HANDLE DEFAULT SRCADDR > %token <v.string> STRING > %token <v.number> NUMBER > %type <v.string> hostcmn > +%type <v.string> srcaddr > %type <v.number> optwrite yesno seclevel socktype > %type <v.data> objtype cmd > %type <v.oid> oid hostoid trapoid > @@ -200,7 +202,8 @@ main : LISTEN ON STRING { > struct address *h; > > TAILQ_INIT(&al); > - if (host($3, &al, 1, SNMPD_PORT, NULL, NULL) <= 0) { > + if (host($3, &al, 1, SNMPD_PORT, NULL, NULL, NULL) > + <= 0) { > yyerror("invalid ip address: %s", $3); > free($3); > YYERROR; > @@ -445,9 +448,13 @@ hostcmn : /* empty */ > { $$ = NULL; } > | COMMUNITY STRING { $$ = $2; } > ; > > -hostdef : STRING hostoid hostcmn { > +srcaddr : /* empty */ { $$ = NULL; } > + | SRCADDR STRING { $$ = $2; } > + ; > + > +hostdef : STRING hostoid hostcmn srcaddr { > if (host($1, hlist, 1, > - SNMPD_TRAPPORT, $2, $3) <= 0) { > + SNMPD_TRAPPORT, $2, $3, $4) <= 0) { > yyerror("invalid host: %s", $1); > free($1); > YYERROR; > @@ -636,6 +643,7 @@ lookup(char *s) > { "seclevel", SECLEVEL }, > { "services", SERVICES }, > { "socket", SOCKET }, > + { "source-address", SRCADDR }, > { "string", OCTETSTRING }, > { "system", SYSTEM }, > { "trap", TRAP }, > @@ -1151,7 +1159,7 @@ host_v6(const char *s) > > int > host_dns(const char *s, struct addresslist *al, int max, > - in_port_t port, struct ber_oid *oid, char *cmn) > + in_port_t port, struct ber_oid *oid, char *cmn, struct address *src) > { > struct addrinfo hints, *res0, *res; > int error, cnt = 0; > @@ -1202,6 +1210,13 @@ host_dns(const char *s, struct addressli > memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) > res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); > } > + if (src != NULL) { > + if (h->ss.ss_family != src->ss.ss_family) { > + log_warnx("host and source-address family > mismatch"); > + return (-1); > + } > + h->sa_srcaddr = src; > + } > > TAILQ_INSERT_HEAD(al, h, entry); > cnt++; > @@ -1220,9 +1235,19 @@ host_dns(const char *s, struct addressli > > int > host(const char *s, struct addresslist *al, int max, > - in_port_t port, struct ber_oid *oid, char *cmn) > + in_port_t port, struct ber_oid *oid, char *cmn, char *srcaddr) > { > - struct address *h; > + struct address *h, *src = NULL; > + > + if (srcaddr != NULL) { > + src = host_v4(srcaddr); > + if (src == NULL) > + src = host_v6(srcaddr); > + if (src == NULL) { > + log_warnx("invalid source-address %s", srcaddr); > + return (-1); > + } > + } > > h = host_v4(s); > > @@ -1234,10 +1259,15 @@ host(const char *s, struct addresslist * > h->port = port; > h->sa_oid = oid; > h->sa_community = cmn; > + if (src != NULL && h->ss.ss_family != src->ss.ss_family) { > + log_warnx("host and source-address family mismatch"); > + return (-1); > + } > + h->sa_srcaddr = src; > > TAILQ_INSERT_HEAD(al, h, entry); > return (1); > } > > - return (host_dns(s, al, max, port, oid, cmn)); > + return (host_dns(s, al, max, port, oid, cmn, src)); > } > 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 30 Sep 2016 13:17:26 -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; > @@ -496,6 +499,7 @@ struct address { > /* For SNMP trap receivers etc. */ > char *sa_community; > struct ber_oid *sa_oid; > + struct address *sa_srcaddr; > }; > TAILQ_HEAD(addresslist, address); > > @@ -766,6 +770,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: trap.c > =================================================================== > RCS file: /cvs/src/usr.sbin/snmpd/trap.c,v > retrieving revision 1.26 > diff -u -p -r1.26 trap.c > --- trap.c 5 Dec 2015 06:42:18 -0000 1.26 > +++ trap.c 30 Sep 2016 13:40:08 -0000 > @@ -201,6 +201,13 @@ trap_send(struct ber_oid *oid, struct be > ret = -1; > goto done; > } > + if (tr->sa_srcaddr != NULL) { > + if (bind(s, (struct sockaddr *)&tr->sa_srcaddr->ss, > + tr->sa_srcaddr->ss.ss_len) == -1) { > + ret = -1; > + goto done; > + } > + } > > cmn = tr->sa_community != NULL ? > tr->sa_community : env->sc_trcommunity; > 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