summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremie Courreges-Anglas <jca@cvs.openbsd.org>2016-11-09 20:31:57 +0000
committerJeremie Courreges-Anglas <jca@cvs.openbsd.org>2016-11-09 20:31:57 +0000
commita2dc634bdd9b931f6de3216693c3408f279fc69d (patch)
tree96792f383109af84c11a64c55284a37401811a96
parentec299ed644b2a87de2b4e31438d9abdfc4efe999 (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.y49
-rw-r--r--usr.sbin/snmpd/snmpd.conf.57
-rw-r--r--usr.sbin/snmpd/snmpd.h10
-rw-r--r--usr.sbin/snmpd/snmpe.c34
-rw-r--r--usr.sbin/snmpd/trap.c9
-rw-r--r--usr.sbin/snmpd/util.c124
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, ...)
{