Jeremie Courreges-Anglas <j...@wxcvbn.org> writes:

> 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.

I decided to go ahead and skip records in case of AF mismatch.  Lightly
tested with:
 - /etc/snmpd.conf
  trap receiver somehostname source-address ::1
 - $ ifconfig lo1
  lo1: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 32768
        index 6 priority 0 llprio 3
        groups: lo
        inet 192.0.2.1 netmask 0xffffff00
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo1 prefixlen 64 scopeid 0x6
        inet6 2001:db8::1 prefixlen 64
 - /etc/hosts
  192.0.2.1       somehostname
  2001:db8::1     somehostname

Comments and test reports still welcome. :)


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 14:15:25 -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;
@@ -1176,6 +1184,8 @@ host_dns(const char *s, struct addressli
                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 addressli
                            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 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 +1256,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.69
diff -u -p -r1.69 snmpd.h
--- snmpd.h     3 Oct 2016 12:16:41 -0000       1.69
+++ snmpd.h     3 Oct 2016 12:57:42 -0000
@@ -392,6 +392,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;
@@ -498,6 +501,7 @@ struct address {
        /* For SNMP trap receivers etc. */
        char                    *sa_community;
        struct ber_oid          *sa_oid;
+       struct address          *sa_srcaddr;
 };
 TAILQ_HEAD(addresslist, address);
 
@@ -768,6 +772,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

Reply via email to