diff options
author | Jeremie Courreges-Anglas <jca@cvs.openbsd.org> | 2016-11-09 20:31:57 +0000 |
---|---|---|
committer | Jeremie Courreges-Anglas <jca@cvs.openbsd.org> | 2016-11-09 20:31:57 +0000 |
commit | a2dc634bdd9b931f6de3216693c3408f279fc69d (patch) | |
tree | 96792f383109af84c11a64c55284a37401811a96 | |
parent | ec299ed644b2a87de2b4e31438d9abdfc4efe999 (diff) |
Improve source IP address handling.
- send replies using a source address equal to the destination address
of queries, using IP_SENDSRCADDR. This help in multihomed setups and
can remove the need to explicitely configure a bind address.
- config knob to set the source address of packets sent to trap
receivers. "trap receiver" gains an optional "source-address"
setting.
Source address issues reported by Andy Lemin. ok benno@
-rw-r--r-- | usr.sbin/snmpd/parse.y | 49 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.conf.5 | 7 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpd.h | 10 | ||||
-rw-r--r-- | usr.sbin/snmpd/snmpe.c | 34 | ||||
-rw-r--r-- | usr.sbin/snmpd/trap.c | 9 | ||||
-rw-r--r-- | usr.sbin/snmpd/util.c | 124 |
6 files changed, 211 insertions, 22 deletions
diff --git a/usr.sbin/snmpd/parse.y b/usr.sbin/snmpd/parse.y index 8ad6fcc46b5..f173650fcab 100644 --- a/usr.sbin/snmpd/parse.y +++ b/usr.sbin/snmpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.39 2016/06/21 21:35:25 benno Exp $ */ +/* $OpenBSD: parse.y,v 1.40 2016/11/09 20:31:56 jca Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org> @@ -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; @@ -1176,6 +1184,8 @@ host_dns(const char *s, struct addresslist *al, int max, if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; + if (src != NULL && src->ss.ss_family != res->ai_family) + continue; if ((h = calloc(1, sizeof(*h))) == NULL) fatal(__func__); @@ -1203,6 +1213,8 @@ host_dns(const char *s, struct addresslist *al, int max, res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); } + h->sa_srcaddr = src; + TAILQ_INSERT_HEAD(al, h, entry); cnt++; } @@ -1220,9 +1232,19 @@ host_dns(const char *s, struct addresslist *al, int max, 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 +1256,15 @@ host(const char *s, struct addresslist *al, int max, 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)); } diff --git a/usr.sbin/snmpd/snmpd.conf.5 b/usr.sbin/snmpd/snmpd.conf.5 index f4c399f4990..1e483ce0c2c 100644 --- a/usr.sbin/snmpd/snmpd.conf.5 +++ b/usr.sbin/snmpd/snmpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: snmpd.conf.5,v 1.34 2015/12/24 16:54:37 mmcc Exp $ +.\" $OpenBSD: snmpd.conf.5,v 1.35 2016/11/09 20:31:56 jca Exp $ .\" .\" Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org> .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: December 24 2015 $ +.Dd $Mdocdate: November 9 2016 $ .Dt SNMPD.CONF 5 .Os .Sh NAME @@ -202,6 +202,7 @@ and any variable bindings contained in the trap .Ic trap receiver Ar string .Op Ic oid Ar oid-string .Op Ic community Ar string +.Op Ic source-address Ar address .Xc Specify the address or FQDN of a remote trap receiver for outgoing traps sent by @@ -212,6 +213,8 @@ configured trap community. The default community is specified by the global .Ic trap community option. +The IPv4 or IPv6 source address of the traps can be enforced using +.Ic source-address . .El .Sh USER CONFIGURATION Users for the SNMP User-based Security Model (USM, RFC 3414) must be diff --git a/usr.sbin/snmpd/snmpd.h b/usr.sbin/snmpd/snmpd.h index 3bd5e61bd75..38d01f0f5bd 100644 --- a/usr.sbin/snmpd/snmpd.h +++ b/usr.sbin/snmpd/snmpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpd.h,v 1.71 2016/10/28 09:07:08 rzalamena Exp $ */ +/* $OpenBSD: snmpd.h,v 1.72 2016/11/09 20:31:56 jca Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org> @@ -405,6 +405,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; @@ -511,6 +514,7 @@ struct address { /* For SNMP trap receivers etc. */ char *sa_community; struct ber_oid *sa_oid; + struct address *sa_srcaddr; }; TAILQ_HEAD(addresslist, address); @@ -787,6 +791,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 *); diff --git a/usr.sbin/snmpd/snmpe.c b/usr.sbin/snmpd/snmpe.c index 2cbb4b9a2b1..23d0dfe32c5 100644 --- a/usr.sbin/snmpd/snmpe.c +++ b/usr.sbin/snmpd/snmpe.c @@ -1,4 +1,4 @@ -/* $OpenBSD: snmpe.c,v 1.44 2016/10/28 09:07:08 rzalamena Exp $ */ +/* $OpenBSD: snmpe.c,v 1.45 2016/11/09 20:31:56 jca Exp $ */ /* * Copyright (c) 2007, 2008, 2012 Reyk Floeter <reyk@openbsd.org> @@ -119,7 +119,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); @@ -130,6 +130,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; @@ -461,8 +481,9 @@ snmpe_recvmsg(int fd, short sig, void *arg) 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; } @@ -550,8 +571,9 @@ snmpe_response(int fd, struct snmp_message *msg) 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++; diff --git a/usr.sbin/snmpd/trap.c b/usr.sbin/snmpd/trap.c index b371e7fafe5..b141a530b87 100644 --- a/usr.sbin/snmpd/trap.c +++ b/usr.sbin/snmpd/trap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: trap.c,v 1.27 2016/10/28 08:01:53 rzalamena Exp $ */ +/* $OpenBSD: trap.c,v 1.28 2016/11/09 20:31:56 jca Exp $ */ /* * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org> @@ -199,6 +199,13 @@ trap_send(struct ber_oid *oid, struct ber_element *elm) 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 : snmpd_env->sc_trcommunity; diff --git a/usr.sbin/snmpd/util.c b/usr.sbin/snmpd/util.c index 2b3dcdac285..288cac3a840 100644 --- a/usr.sbin/snmpd/util.c +++ b/usr.sbin/snmpd/util.c @@ -1,4 +1,4 @@ -/* $OpenBSD: util.c,v 1.5 2015/11/21 13:06:22 reyk Exp $ */ +/* $OpenBSD: util.c,v 1.6 2016/11/09 20:31:56 jca Exp $ */ /* * Copyright (c) 2014 Bret Stephen Lambert <blambert@openbsd.org> * @@ -156,6 +156,128 @@ varbind_convert(struct agentx_pdu *pdu, struct agentx_varbind_hdr *vbhdr, 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, ...) { |