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)