On Wed, Jul 19, 2017 at 11:15:45AM +0200, Reyk Floeter wrote:
> 
> > On 19.07.2017, at 10:16, Marco Pfatschbacher <m...@mailq.de> wrote:
> > 
> > This adds TCP support to snmpd.
> > I've added a tcp option to the "listen on" statement.
> > The trap receiver will continue to bind to UDP addresses only.
> > 
> > Tested against net-snmp, which has TCP support.
> > 
> 
> Nice!
> 
> Are you also planning to add tcp support in snmpctl?

Hmm, haven't thought about it :-) 

> And it would also be nice to get tls (over TCP) later, there is an RFC for 
> that,
> but the tcp implementation can already be done with tls in mind.

Isn't that covered with USM?
However, I'm tunneling through SSH anyway. That's why I needed TCP.

> I will give it a try, see comments below.

Thanks,
I changed most of them.
> > 
> > @@ -99,10 +107,20 @@ snmpe_init(struct privsep *ps, struct pr
> >     timer_init();
> >     usm_generate_keys();
> > 
> > -   /* listen for incoming SNMP UDP messages */
> > +   timerclear(&snmpe_tcp_timeout);
> > +   snmpe_tcp_timeout.tv_sec = SNMPE_TCP_TIMEOUT;
> 
> The comment says UDP but the variables are for TCP?

That's why I deleted it ;)

> > +
> > +   /* listen for incoming SNMP UDP/TCP messages */
> >     TAILQ_FOREACH(so, &env->sc_sockets, entry) {
> > -           event_set(&so->s_ev, so->s_fd, EV_READ|EV_PERSIST,
> > -               snmpe_recvmsg, env);
> > +           if (so->s_ipproto == IPPROTO_TCP) {
> > +                   if (listen(so->s_fd, 5) < 0)
> > +                           fatalx("snmpe: failed to listen on socket");
> > +                   event_set(&so->s_ev, so->s_fd, EV_READ|EV_PERSIST,
> > +                       snmpe_accept_cb, env);
> > +           } else {
> > +                   event_set(&so->s_ev, so->s_fd, EV_READ|EV_PERSIST,
> > +                       snmpe_recvmsg, env);
> > +           }
> >             event_add(&so->s_ev, NULL);
> >     }
> > }
> > @@ -110,6 +128,12 @@ snmpe_init(struct privsep *ps, struct pr
> > void
> > snmpe_shutdown(void)
> > {
> > +   struct listen_sock *so;
> > +
> > +   TAILQ_FOREACH(so, &snmpd_env->sc_sockets, entry) {
> > +           event_del(&so->s_ev);
> > +           close(so->s_fd);
> > +   }
> >     kr_shutdown();
> > }
> > 
> > @@ -130,32 +154,43 @@ snmpe_bind(struct address *addr)
> >     char     buf[512];
> >     int      val, s;
> > 
> > -   if ((s = snmpd_socket_af(&addr->ss, htons(addr->port))) == -1)
> > +   if ((s = snmpd_socket_af(&addr->ss, htons(addr->port),
> > +       addr->ipproto)) == -1)
> >             return (-1);
> > 
> >     /*
> >      * Socket options
> >      */
> > -   if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
> > -           goto bad;
> 
> So are you using blocking listening TCP sockets?

Yes, since we are using libevent.
But I changed them anyway, in case there is a race.

> We also have fd accounting in a few daemons to pause accept when
> we're out of fds (ENFILE or EMFILE), but this can be implemented in
> a separate commit.

Not sure what that solves. sshd has MaxStartups and it's very
easy to DoS it. 

> 
> best practice is to avoid EV_PERSIST and to always re-schedule when
> needed. This prevents us from having stale events.

OK. I left it for the accept callbacks though. 

> > +   len = read(fd, msg->sm_data + msg->sm_datalen,
> > +       sizeof(msg->sm_data) - msg->sm_datalen);
> > +   if (len == 0) {
> 
> How are you handling error cases len == -1?

Thanks, I missed that.

> And it is non-blocking I/O so you have to check for EINTR and EAGAIN.
> 
> > +           event_del(&msg->sm_sockev);
> > +           close(fd);
> > +           snmp_msgfree(msg);
> > +           return;
> > +   }
> > +
> > +   msg->sm_datalen = (size_t)len;
> > +   msg->sm_ber.fd = -1;
> > +   ber_set_application(&msg->sm_ber, smi_application);
> > +   ber_set_readbuf(&msg->sm_ber, msg->sm_data, msg->sm_datalen);
> > +   msg->sm_req = ber_read_elements(&msg->sm_ber, NULL);
> > +   if (msg->sm_req == NULL)
> > +           return; /* short read; try again */
> 
> So you basically keep on retrying if anything goes wrong?
> 
> Will your persist event continue to fire if len returned -1 on read error?

Fixed. 

> > +void
> > +snmpe_writecb(int fd, short type, void *arg)
> > +{
> > +   struct snmp_stats       *stats = &snmpd_env->sc_stats;
> > +   struct snmp_message *msg = arg;
> > +   ssize_t len;
> > +   struct ber *ber = &msg->sm_ber;
> > +
> > +   if (type == EV_TIMEOUT)
> > +           goto fail;
> > +
> > +   len = ber->br_wend - ber->br_wbuf; // XXX might be wrong on reuse
> 
> // XXX?

I forgot to remove that comment.
We don't reuse the msg/ber between two writes, so it's ok.
I'm still not too happy for using the ber to track progress though.

> > +   ber->br_wptr = ber->br_wbuf;
> > +
> > +   log_debug("%s: write fd %d len %zd", __func__, fd, len);
> > +
> > +   len = write(fd, ber->br_wptr, len);
> > +   if (len == -1)
> > +           goto fail;
> > +
> > +   ber->br_wptr += len;
> > +
> > +   if (ber->br_wptr < ber->br_wend)
> > +           return;


New diff below


Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/parse.y,v
retrieving revision 1.43
diff -u -p -p -u -r1.43 parse.y
--- parse.y     5 Jan 2017 13:53:10 -0000       1.43
+++ parse.y     20 Jul 2017 09:37:25 -0000
@@ -99,9 +99,9 @@ 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 *,
-                   struct address *);
+                   struct address *, int);
 int             host(const char *, struct addresslist *,
-                   int, in_port_t, struct ber_oid *, char *, char *);
+                   int, in_port_t, struct ber_oid *, char *, char *, int);
 
 typedef struct {
        union {
@@ -128,12 +128,12 @@ 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 SRCADDR
+%token SOCKET RESTRICTED AGENTX HANDLE DEFAULT SRCADDR TCP UDP
 %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.number>      optwrite yesno seclevel socktype proto
 %type  <v.data>        objtype cmd
 %type  <v.oid>         oid hostoid trapoid
 %type  <v.auth>        auth
@@ -197,9 +197,9 @@ yesno               :  STRING                       {
                }
                ;
 
-main           : LISTEN ON STRING              {
+main           : LISTEN ON STRING proto        {
                        if (host($3, &conf->sc_addresses, 16, SNMPD_PORT, NULL,
-                           NULL, NULL) <= 0) {
+                           NULL, NULL, $4) <= 0) {
                                yyerror("invalid ip address: %s", $3);
                                free($3);
                                YYERROR;
@@ -442,7 +442,7 @@ srcaddr             : /* empty */                           
{ $$ = NULL; }
 
 hostdef                : STRING hostoid hostcmn srcaddr        {
                        if (host($1, hlist, 1,
-                           SNMPD_TRAPPORT, $2, $3, $4) <= 0) {
+                           SNMPD_TRAPPORT, $2, $3, $4, 0) <= 0) {
                                yyerror("invalid host: %s", $1);
                                free($1);
                                YYERROR;
@@ -524,6 +524,11 @@ socktype   : RESTRICTED            { $$ = SOCK_TYPE_
                | /* nothing */         { $$ = 0; }
                ;
 
+proto          : /* empty */                   { $$ = IPPROTO_UDP; }
+               | TCP                           { $$ = IPPROTO_TCP; }
+               | UDP                           { $$ = IPPROTO_UDP; }
+               ;
+
 cmd            : STRING                {
                        struct           trapcmd *cmd;
                        size_t           span, limit;
@@ -634,7 +639,9 @@ lookup(char *s)
                { "source-address",     SRCADDR },
                { "string",             OCTETSTRING },
                { "system",             SYSTEM },
+               { "tcp",                TCP },
                { "trap",               TRAP },
+               { "udp",                UDP },
                { "user",               USER }
        };
        const struct keywords   *p;
@@ -969,6 +976,8 @@ struct snmpd *
 parse_config(const char *filename, u_int flags)
 {
        struct sym      *sym, *next;
+       struct address  *h;
+       int found;
 
        if ((conf = calloc(1, sizeof(*conf))) == NULL) {
                log_warn("cannot allocate memory");
@@ -999,18 +1008,25 @@ parse_config(const char *filename, u_int
 
        endservent();
 
+       /* Setup default listen addresses */
        if (TAILQ_EMPTY(&conf->sc_addresses)) {
-               struct address          *h;
-               if ((h = calloc(1, sizeof(*h))) == NULL)
-                       fatal("snmpe: %s", __func__);
-               h->ss.ss_family = AF_INET;
-               h->port = SNMPD_PORT;
-               TAILQ_INSERT_TAIL(&conf->sc_addresses, h, entry);
-               if ((h = calloc(1, sizeof(*h))) == NULL)
-                       fatal("snmpe: %s", __func__);
-               h->ss.ss_family = AF_INET6;
-               h->port = SNMPD_PORT;
-               TAILQ_INSERT_TAIL(&conf->sc_addresses, h, entry);
+               host("0.0.0.0", &conf->sc_addresses, 1, SNMPD_PORT,
+                   NULL, NULL, NULL, IPPROTO_UDP);
+               host("::", &conf->sc_addresses, 1, SNMPD_PORT,
+                   NULL, NULL, NULL, IPPROTO_UDP);
+       }
+       if (conf->sc_traphandler) {
+               found = 0;
+               TAILQ_FOREACH(h, &conf->sc_addresses, entry) {
+                       if (h->ipproto == IPPROTO_UDP)
+                               found = 1;
+               }
+               if (!found) {
+                       log_warnx("trap handler needs at least one "
+                           "udp listen address");
+                       free(conf);
+                       return (NULL);
+               }
        }
 
        /* Free macros and check which have not been used. */
@@ -1162,7 +1178,8 @@ 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, struct address *src)
+    in_port_t port, struct ber_oid *oid, char *cmn,
+    struct address *src, int ipproto)
 {
        struct addrinfo          hints, *res0, *res;
        int                      error, cnt = 0;
@@ -1193,6 +1210,7 @@ host_dns(const char *s, struct addressli
                        fatal(__func__);
 
                h->port = port;
+               h->ipproto = ipproto;
                if (oid != NULL) {
                        if ((h->sa_oid = calloc(1, sizeof(*oid))) == NULL)
                                fatal(__func__);
@@ -1235,7 +1253,7 @@ 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, char *srcaddr)
+    in_port_t port, struct ber_oid *oid, char *cmn, char *srcaddr, int ipproto)
 {
        struct address  *h, *src = NULL;
 
@@ -1259,6 +1277,7 @@ host(const char *s, struct addresslist *
                h->port = port;
                h->sa_oid = oid;
                h->sa_community = cmn;
+               h->ipproto = ipproto;
                if (src != NULL && h->ss.ss_family != src->ss.ss_family) {
                        log_warnx("host and source-address family mismatch");
                        return (-1);
@@ -1269,5 +1288,5 @@ host(const char *s, struct addresslist *
                return (1);
        }
 
-       return (host_dns(s, al, max, port, oid, cmn, src));
+       return (host_dns(s, al, max, port, oid, cmn, src, ipproto));
 }
Index: snmpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.c,v
retrieving revision 1.36
diff -u -p -p -u -r1.36 snmpd.c
--- snmpd.c     4 Apr 2017 02:37:15 -0000       1.36
+++ snmpd.c     20 Jul 2017 09:37:25 -0000
@@ -310,7 +310,7 @@ snmpd_dispatch_snmpe(int fd, struct priv
 }
 
 int
-snmpd_socket_af(struct sockaddr_storage *ss, in_port_t port)
+snmpd_socket_af(struct sockaddr_storage *ss, in_port_t port, int ipproto)
 {
        int      s;
 
@@ -329,7 +329,13 @@ snmpd_socket_af(struct sockaddr_storage 
                return (-1);
        }
 
-       s = socket(ss->ss_family, SOCK_DGRAM, IPPROTO_UDP);
+       if (ipproto == IPPROTO_TCP)
+               s = socket(ss->ss_family,
+                   SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
+       else
+               s = socket(ss->ss_family,
+                   SOCK_DGRAM|SOCK_NONBLOCK|SOCK_CLOEXEC, IPPROTO_UDP);
+
        return (s);
 }
 
Index: snmpd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.conf.5,v
retrieving revision 1.35
diff -u -p -p -u -r1.35 snmpd.conf.5
--- snmpd.conf.5        9 Nov 2016 20:31:56 -0000       1.35
+++ snmpd.conf.5        20 Jul 2017 09:37:25 -0000
@@ -86,10 +86,13 @@ Routing table information will not be av
 reduced during bulk updates.
 The default is
 .Ic no .
-.It Ic listen on Ar address
+.It Ic listen on Ar address Op Ic tcp | udp
 Specify the local address
 .Xr snmpd 8
 should listen on for incoming SNMP messages.
+Multiple
+.Ic listen on
+statements are supported, the default is UDP.
 .It Ic read-only community Ar string
 Specify the name of the read-only community.
 The default value is
@@ -198,6 +201,9 @@ the resolved hostname of the host sendin
 the IP address of the host sending the trap,
 and any variable bindings contained in the trap
 (the OID followed by the value, separated by a single space).
+Traps will will be accepted on all
+.Ic listen on
+UDP addresses.
 .It Xo
 .Ic trap receiver Ar string
 .Op Ic oid Ar oid-string
Index: snmpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpd.h,v
retrieving revision 1.75
diff -u -p -p -u -r1.75 snmpd.h
--- snmpd.h     21 Apr 2017 13:50:23 -0000      1.75
+++ snmpd.h     20 Jul 2017 09:37:26 -0000
@@ -403,6 +403,8 @@ struct pfr_buffer {
 struct snmp_message {
        int                      sm_sock;
        struct sockaddr_storage  sm_ss;
+       int                      sm_sock_tcp;
+       struct event             sm_sockev;
        socklen_t                sm_slen;
        char                     sm_host[HOST_NAME_MAX+1];
 
@@ -509,6 +511,7 @@ struct snmp_stats {
 struct address {
        struct sockaddr_storage  ss;
        in_port_t                port;
+       int                      ipproto;
 
        TAILQ_ENTRY(address)     entry;
 
@@ -521,6 +524,7 @@ TAILQ_HEAD(addresslist, address);
 
 struct listen_sock {
        int                             s_fd;
+       int                             s_ipproto;
        struct event                    s_ev;
        TAILQ_ENTRY(listen_sock)        entry;
 };
@@ -739,7 +743,7 @@ char                *smi_print_element(struct ber_elem
 void            timer_init(void);
 
 /* snmpd.c */
-int             snmpd_socket_af(struct sockaddr_storage *, in_port_t);
+int             snmpd_socket_af(struct sockaddr_storage *, in_port_t, int);
 u_long          snmpd_engine_time(void);
 char           *tohexstr(u_int8_t *, int);
 
Index: snmpe.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/snmpe.c,v
retrieving revision 1.47
diff -u -p -p -u -r1.47 snmpe.c
--- snmpe.c     21 Apr 2017 13:50:23 -0000      1.47
+++ snmpe.c     20 Jul 2017 09:37:26 -0000
@@ -49,10 +49,16 @@ void         snmpe_sig_handler(int sig, short, 
 int     snmpe_dispatch_parent(int, struct privsep_proc *, struct imsg *);
 int     snmpe_bind(struct address *);
 void    snmpe_recvmsg(int fd, short, void *);
+void    snmpe_readcb(int fd, short, void *);
+void    snmpe_writecb(int fd, short, void *);
+void    snmpe_accept_cb(int fd, short, void *);
+void    snmpe_prepare_read(struct snmp_message *, int);
 int     snmpe_encode(struct snmp_message *);
 void    snmp_msgfree(struct snmp_message *);
 
 struct imsgev  *iev_parent;
+struct timeval snmpe_tcp_timeout;
+#define SNMPE_TCP_TIMEOUT 10
 
 static struct privsep_proc procs[] = {
        { "parent",     PROC_PARENT,    snmpe_dispatch_parent }
@@ -76,11 +82,13 @@ snmpe(struct privsep *ps, struct privsep
        }
 #endif
 
+       /* bind SNMP UDP/TCP sockets */
        TAILQ_FOREACH(h, &env->sc_addresses, entry) {
                if ((so = calloc(1, sizeof(*so))) == NULL)
                        fatal("snmpe: %s", __func__);
                if ((so->s_fd = snmpe_bind(h)) == -1)
-                       fatal("snmpe: failed to bind SNMP UDP socket");
+                       fatal("snmpe: failed to bind SNMP socket");
+               so->s_ipproto = h->ipproto;
                TAILQ_INSERT_TAIL(&env->sc_sockets, so, entry);
        }
 
@@ -99,10 +107,20 @@ snmpe_init(struct privsep *ps, struct pr
        timer_init();
        usm_generate_keys();
 
-       /* listen for incoming SNMP UDP messages */
+       timerclear(&snmpe_tcp_timeout);
+       snmpe_tcp_timeout.tv_sec = SNMPE_TCP_TIMEOUT;
+
+       /* listen for incoming SNMP UDP/TCP messages */
        TAILQ_FOREACH(so, &env->sc_sockets, entry) {
-               event_set(&so->s_ev, so->s_fd, EV_READ|EV_PERSIST,
-                   snmpe_recvmsg, env);
+               if (so->s_ipproto == IPPROTO_TCP) {
+                       if (listen(so->s_fd, 5) < 0)
+                               fatalx("snmpe: failed to listen on socket");
+                       event_set(&so->s_ev, so->s_fd, EV_READ|EV_PERSIST,
+                           snmpe_accept_cb, env);
+               } else {
+                       event_set(&so->s_ev, so->s_fd, EV_READ|EV_PERSIST,
+                           snmpe_recvmsg, env);
+               }
                event_add(&so->s_ev, NULL);
        }
 }
@@ -110,6 +128,12 @@ snmpe_init(struct privsep *ps, struct pr
 void
 snmpe_shutdown(void)
 {
+       struct listen_sock *so;
+
+       TAILQ_FOREACH(so, &snmpd_env->sc_sockets, entry) {
+               event_del(&so->s_ev);
+               close(so->s_fd);
+       }
        kr_shutdown();
 }
 
@@ -130,32 +154,34 @@ snmpe_bind(struct address *addr)
        char     buf[512];
        int      val, s;
 
-       if ((s = snmpd_socket_af(&addr->ss, htons(addr->port))) == -1)
+       if ((s = snmpd_socket_af(&addr->ss, htons(addr->port),
+           addr->ipproto)) == -1)
                return (-1);
 
-       /*
-        * Socket options
-        */
-       if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
-               goto bad;
-
-       switch (addr->ss.ss_family) {
-       case AF_INET:
+       if (addr->ipproto == IPPROTO_TCP) {
                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 (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                   &val, sizeof(val)) == -1)
+                       fatal("setsockopt: %s", strerror(errno));
+       } else { /* UDP */
+               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;
+                       }
                }
        }
 
@@ -165,7 +191,8 @@ snmpe_bind(struct address *addr)
        if (print_host(&addr->ss, buf, sizeof(buf)) == NULL)
                goto bad;
 
-       log_info("snmpe: listening on %s:%d", buf, addr->port);
+       log_info("snmpe: listening on %s %s:%d",
+           (addr->ipproto == IPPROTO_TCP) ? "tcp" : "udp", buf, addr->port);
 
        return (s);
 
@@ -479,6 +506,138 @@ snmpe_parsevarbinds(struct snmp_message 
 }
 
 void
+snmpe_accept_cb(int fd, short type, void *arg)
+{
+       int afd;
+       socklen_t addrlen;
+       struct sockaddr addr;
+       struct snmp_message     *msg = NULL;
+
+       if ((afd = accept4(fd, &addr, &addrlen,
+           SOCK_NONBLOCK|SOCK_CLOEXEC)) < 0) {
+               if (errno != EWOULDBLOCK && errno != EAGAIN)
+                       log_warnx("%s: accept4: %s", __func__, strerror(errno));
+               return;
+       }
+       if ((msg = calloc(1, sizeof(*msg))) == NULL)
+               goto fail;
+
+       snmpe_prepare_read(msg, afd);
+       return;
+fail:
+       free(msg);
+       close(afd);
+       return;
+}
+
+void
+snmpe_prepare_read(struct snmp_message *msg, int fd)
+{
+       msg->sm_sock = fd;
+       msg->sm_sock_tcp = 1;
+       msg->sm_ber.fd = -1;
+       event_set(&msg->sm_sockev, fd, EV_READ,
+           snmpe_readcb, msg);
+       event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
+}
+
+void
+snmpe_readcb(int fd, short type, void *arg)
+{
+       struct snmp_stats       *stats = &snmpd_env->sc_stats;
+       struct snmp_message *msg = arg;
+       ssize_t len;
+
+       if (type == EV_TIMEOUT) {
+               snmp_msgfree(msg);
+               close(fd);
+               return;
+       }
+       len = read(fd, msg->sm_data + msg->sm_datalen,
+           sizeof(msg->sm_data) - msg->sm_datalen);
+       if (len <= 0) {
+               if (errno != EAGAIN && errno != EINTR) {
+                       snmp_msgfree(msg);
+                       close(fd);
+                       return;
+               }
+               snmpe_prepare_read(msg, fd);
+               return;
+       }
+
+       msg->sm_datalen += (size_t)len;
+       msg->sm_ber.fd = -1;
+       ber_set_application(&msg->sm_ber, smi_application);
+       ber_set_readbuf(&msg->sm_ber, msg->sm_data, msg->sm_datalen);
+       msg->sm_req = ber_read_elements(&msg->sm_ber, NULL);
+       if (msg->sm_req == NULL) {
+               /* short read; try again */
+               snmpe_prepare_read(msg, fd);
+               return;
+       }
+
+       if (snmpe_parse(msg) == -1) {
+               if (msg->sm_usmerr && MSG_REPORT(msg)) {
+                       usm_make_report(msg);
+                       snmpe_response(msg);
+                       return;
+               } else {
+                       snmp_msgfree(msg);
+                       close(fd);
+                       return;
+               }
+       }
+       stats->snmp_inpkts++;
+
+       snmpe_dispatchmsg(msg);
+}
+
+void
+snmpe_writecb(int fd, short type, void *arg)
+{
+       struct snmp_stats       *stats = &snmpd_env->sc_stats;
+       struct snmp_message *msg = arg;
+       ssize_t len;
+       struct ber *ber = &msg->sm_ber;
+
+       if (type == EV_TIMEOUT)
+               goto fail;
+
+       len = ber->br_wend - ber->br_wbuf;
+       ber->br_wptr = ber->br_wbuf;
+
+       log_debug("%s: write fd %d len %zd", __func__, fd, len);
+
+       len = write(fd, ber->br_wptr, len);
+       if (len == -1 && errno != EAGAIN && errno != EINTR)
+               goto fail;
+
+       ber->br_wptr += len;
+
+       if (ber->br_wptr < ber->br_wend) {
+               event_set(&msg->sm_sockev, msg->sm_sock, EV_WRITE,
+                   snmpe_writecb, msg);
+               event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
+               return;
+       }
+
+       stats->snmp_outpkts++;
+       snmp_msgfree(msg);
+
+       /* socket can be reused; wait for another message */
+       if ((msg = calloc(1, sizeof(*msg))) == NULL) {
+               close(fd);
+               return;
+       }
+       snmpe_prepare_read(msg, fd);
+       return;
+
+ fail:
+       close(fd);
+       snmp_msgfree(msg);
+}
+
+void
 snmpe_recvmsg(int fd, short sig, void *arg)
 {
        struct snmpd            *env = arg;
@@ -535,10 +694,11 @@ snmpe_recvmsg(int fd, short sig, void *a
 void
 snmpe_dispatchmsg(struct snmp_message *msg)
 {
+       /* dispatched to subagent */
        if (snmpe_parsevarbinds(msg) == 1)
                return;
 
-       /* not dispatched to subagent; respond directly */
+       /* respond directly */
        msg->sm_context = SNMP_C_GETRESP;
        snmpe_response(msg);
 }
@@ -581,11 +741,18 @@ snmpe_response(struct snmp_message *msg)
                goto done;
 
        usm_finalize_digest(msg, ptr, len);
-       len = sendtofrom(msg->sm_sock, 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++;
+       if (msg->sm_sock_tcp) {
+               event_set(&msg->sm_sockev, msg->sm_sock, EV_WRITE,
+                   snmpe_writecb, msg);
+               event_add(&msg->sm_sockev, &snmpe_tcp_timeout);
+               return;
+       } else {
+               len = sendtofrom(msg->sm_sock, 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++;
+       }
 
  done:
        snmp_msgfree(msg);
Index: trap.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/trap.c,v
retrieving revision 1.29
diff -u -p -p -u -r1.29 trap.c
--- trap.c      21 Apr 2017 13:46:15 -0000      1.29
+++ trap.c      20 Jul 2017 09:37:26 -0000
@@ -196,7 +196,8 @@ trap_send(struct ber_oid *oid, struct be
                                continue;
                }
 
-               if ((s = snmpd_socket_af(&tr->ss, htons(tr->port))) == -1) {
+               if ((s = snmpd_socket_af(&tr->ss, htons(tr->port),
+                   IPPROTO_UDP)) == -1) {
                        ret = -1;
                        goto done;
                }
Index: traphandler.c
===================================================================
RCS file: /cvs/src/usr.sbin/snmpd/traphandler.c,v
retrieving revision 1.8
diff -u -p -p -u -r1.8 traphandler.c
--- traphandler.c       9 Jan 2017 14:49:22 -0000       1.8
+++ traphandler.c       20 Jul 2017 09:37:26 -0000
@@ -79,6 +79,8 @@ traphandler(struct privsep *ps, struct p
 
        if (env->sc_traphandler) {
                TAILQ_FOREACH(h, &env->sc_addresses, entry) {
+                       if (h->ipproto != IPPROTO_UDP)
+                               continue;
                        if ((so = calloc(1, sizeof(*so))) == NULL)
                                fatal("%s", __func__);
                        if ((so->s_fd = traphandler_bind(h)) == -1)
@@ -113,7 +115,8 @@ traphandler_bind(struct address *addr)
        int                      s;
        char                     buf[512];
 
-       if ((s = snmpd_socket_af(&addr->ss, htons(SNMPD_TRAPPORT))) == -1)
+       if ((s = snmpd_socket_af(&addr->ss, htons(SNMPD_TRAPPORT),
+           IPPROTO_UDP)) == -1)
                return (-1);
 
        if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)

Reply via email to