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.

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     19 Jul 2017 07:53:45 -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;
@@ -999,18 +1006,26 @@ 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) {
+               struct address  *h;
+               int found = 0;
+               TAILQ_FOREACH(h, &conf->sc_addresses, entry) {
+                       if (h->ipproto == IPPROTO_UDP)
+                               found = 1;
+               }
+               if (!found) {
+                       fprintf(stderr, "trap handler needs at least one "
+                           "udp listen address\n");
+                       free(conf);
+                       return (NULL);
+               }
        }
 
        /* Free macros and check which have not been used. */
@@ -1162,7 +1177,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 +1209,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 +1252,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 +1276,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 +1287,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     19 Jul 2017 07:53:45 -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,11 @@ 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, 0);
+       else
+               s = socket(ss->ss_family, SOCK_DGRAM, 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        19 Jul 2017 07:53:45 -0000
@@ -86,7 +86,7 @@ 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.
@@ -198,6 +198,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     19 Jul 2017 07:53:45 -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     19 Jul 2017 07:53:45 -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_event(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,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;
-
-       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:
+       if (addr->ipproto == IPPROTO_TCP) {
+               if (fcntl(s, F_SETFD, 1) == -1)
+                       fatal("fcntl(%d, F_SETFD)", s);
                val = 1;
-               if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO,
-                   &val, sizeof(int)) == -1) {
-                       log_warn("%s: failed to set IPv6 packet info",
-                           __func__);
+               if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                   &val, sizeof(val)) == -1)
+                       fatal("setsockopt: %s", strerror(errno));
+       } else {
+               /* UDP */
+               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;
+                       }
                }
        }
 
@@ -165,7 +200,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 +515,131 @@ 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 = accept(fd, &addr, &addrlen)) < 0) {
+               log_warnx("%s: accept: %s", __func__, strerror(errno));
+               return;
+       }
+       if (fcntl(afd, F_SETFL, O_NONBLOCK) < 0) {
+               log_warnx("%s: fcntl: %s", __func__, strerror(errno));
+               goto fail;
+       }
+       if ((msg = calloc(1, sizeof(*msg))) == NULL)
+               goto fail;
+
+       snmpe_prepare_read_event(msg, afd);
+       return;
+fail:
+       free(msg);
+       close(afd);
+       return;
+}
+
+void
+snmpe_prepare_read_event(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|EV_PERSIST,
+           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) {
+               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 */
+
+       event_del(&msg->sm_sockev);
+       if (snmpe_parse(msg) == -1) {
+               if (msg->sm_usmerr != 0 && MSG_REPORT(msg)) {
+                       usm_make_report(msg);
+                       snmpe_response(msg);
+                       return;
+               } else {
+                       snmp_msgfree(msg);
+                       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; // XXX might be wrong on reuse
+       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;
+
+       stats->snmp_outpkts++;
+       event_del(&msg->sm_sockev);
+       snmp_msgfree(msg);
+
+       /* socket can be reused; wait for another message */
+       if ((msg = calloc(1, sizeof(*msg))) == NULL) {
+               close(fd);
+               return;
+       }
+       snmpe_prepare_read_event(msg, fd);
+       return;
+
+fail:
+       event_del(&msg->sm_sockev);
+       close(fd);
+       snmp_msgfree(msg);
+}
+
+void
 snmpe_recvmsg(int fd, short sig, void *arg)
 {
        struct snmpd            *env = arg;
@@ -535,10 +696,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 +743,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|EV_PERSIST,
+                   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      19 Jul 2017 07:53:45 -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       19 Jul 2017 07:53:45 -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