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

Reply via email to