ee2e9f4dbc
The code that sets up msghdr must first fully fill in the msghdr itself, and only then use CMSG_xxx() macros. Silence from: harti, one week
1345 lines
31 KiB
C
1345 lines
31 KiB
C
/*
|
|
* Copyright (c) 2018
|
|
* Hartmut Brandt.
|
|
* All rights reserved.
|
|
*
|
|
* Author: Harti Brandt <harti@freebsd.org>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $Begemot: bsnmp/snmpd/trans_udp.c,v 1.5 2005/10/04 08:46:56 brandt_h Exp $
|
|
*
|
|
* Internet transport
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include "asn1.h"
|
|
#include "snmp.h"
|
|
#include "snmpmod.h"
|
|
|
|
#include "snmpd.h"
|
|
|
|
#define SNMPTREE_TYPES
|
|
#define SNMPENUM_FUNCS
|
|
#include "tree.h"
|
|
#include "oid.h"
|
|
|
|
extern const struct transport_def inet_trans;
|
|
|
|
struct inet_port;
|
|
struct inet_port_params;
|
|
struct port_sock;
|
|
|
|
typedef int create_func(struct inet_port *, struct inet_port_params *);
|
|
typedef void input_func(int, void *);
|
|
typedef int activate_func(struct inet_port *);
|
|
typedef void deactivate_func(struct inet_port *);
|
|
typedef void parse_ctrl_func(struct port_sock *, const struct msghdr *);
|
|
typedef void setsrc_func(struct port_sock *, struct msghdr *, char *);
|
|
|
|
static create_func ipv4_create;
|
|
static input_func ipv4_input;
|
|
static activate_func ipv4_activate;
|
|
static deactivate_func ipv4_deactivate;
|
|
static parse_ctrl_func ipv4_parse_ctrl;
|
|
static setsrc_func ipv4_setsrc;
|
|
|
|
static create_func ipv6_create;
|
|
static input_func ipv6_input;
|
|
static activate_func ipv6_activate;
|
|
static deactivate_func ipv6_deactivate;
|
|
static parse_ctrl_func ipv6_parse_ctrl;
|
|
static setsrc_func ipv6_setsrc;
|
|
|
|
static create_func ipv6z_create;
|
|
|
|
static create_func dns_create;
|
|
static activate_func dns_activate;
|
|
static deactivate_func dns_deactivate;
|
|
|
|
struct port_sock {
|
|
/* common input stuff; must be first */
|
|
struct port_input input;
|
|
|
|
/** link field */
|
|
TAILQ_ENTRY(port_sock) link;
|
|
|
|
/** pointer to parent */
|
|
struct inet_port *port;
|
|
|
|
/** bind address */
|
|
struct sockaddr_storage bind_addr;
|
|
|
|
/** reply destination */
|
|
struct sockaddr_storage ret_dest;
|
|
|
|
/** need to set source address in reply; set for INADDR_ANY */
|
|
bool set_ret_source;
|
|
|
|
/** address of the receive interface */
|
|
union {
|
|
/** IPv4 case */
|
|
struct in_addr a4;
|
|
|
|
/** IPv6 case */
|
|
struct in6_pktinfo a6;
|
|
} ret_source;
|
|
|
|
/** parse control message */
|
|
parse_ctrl_func *parse_ctrl;
|
|
|
|
/** set source address for a send() */
|
|
setsrc_func *setsrc;
|
|
};
|
|
static_assert(offsetof(struct port_sock, input) == 0,
|
|
"input not first in port_sock");
|
|
|
|
/**
|
|
* Table row for the inet ports.
|
|
*
|
|
* When actived each row can have one or several open sockets. For ipv6,
|
|
* ipv4 and ipv6z addresses it is always one, for dns addresses more than
|
|
* one socket can be open.
|
|
*/
|
|
struct inet_port {
|
|
/** common i/o port stuff (must be first) */
|
|
struct tport tport;
|
|
|
|
/** transport protocol */
|
|
enum BegemotSnmpdTransportProto proto;
|
|
|
|
/** row status */
|
|
enum RowStatus row_status;
|
|
|
|
/** socket list */
|
|
TAILQ_HEAD(, port_sock) socks;
|
|
|
|
/** value for InetAddressType::dns */
|
|
char *dns_addr;
|
|
|
|
/** port number in dns case; network byte order */
|
|
uint16_t dns_port;
|
|
|
|
/** create a port */
|
|
create_func *create;
|
|
|
|
/** activate a port */
|
|
activate_func *activate;
|
|
|
|
/** deactivate port */
|
|
deactivate_func *deactivate;
|
|
};
|
|
static_assert(offsetof(struct inet_port, tport) == 0,
|
|
"tport not first in inet_port");
|
|
|
|
/** to be used in bind_addr field */
|
|
#define AF_DNS AF_VENDOR00
|
|
|
|
/** registered transport */
|
|
static struct transport *my_trans;
|
|
|
|
/** set operation */
|
|
enum {
|
|
SET_CREATED,
|
|
SET_ACTIVATED,
|
|
SET_DEACTIVATE,
|
|
SET_DESTROY,
|
|
};
|
|
|
|
/** length of the control data buffer */
|
|
static const size_t RECV_CBUF_SIZE =
|
|
MAX(CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) +
|
|
CMSG_SPACE(sizeof(struct in_addr)),
|
|
CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) +
|
|
CMSG_SPACE(sizeof(struct in6_pktinfo)));
|
|
|
|
/** length of the control data buffer */
|
|
static const size_t XMIT_CBUF_SIZE =
|
|
MAX(CMSG_SPACE(sizeof(struct in_addr)),
|
|
CMSG_SPACE(sizeof(struct in6_pktinfo)));
|
|
|
|
/**
|
|
* Start the transport. This registers the transport with the
|
|
* transport table.
|
|
*
|
|
* \return SNMP error code
|
|
*/
|
|
static int
|
|
inet_start(void)
|
|
{
|
|
return (trans_register(&inet_trans, &my_trans));
|
|
}
|
|
|
|
/**
|
|
* Stop the transport. This tries to unregister the transport which
|
|
* in turn fails if the list of ports is not empty.
|
|
*
|
|
* \return SNMP error code
|
|
*/
|
|
static int
|
|
inet_stop(int force __unused)
|
|
{
|
|
if (my_trans != NULL)
|
|
if (trans_unregister(my_trans) != 0)
|
|
return (SNMP_ERR_GENERR);
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
|
|
/**
|
|
* Deactivate SNMP port.
|
|
*
|
|
* \param tp port to close
|
|
*/
|
|
static void
|
|
deactivate_port(struct inet_port *p)
|
|
{
|
|
p->deactivate(p);
|
|
}
|
|
|
|
/*
|
|
* This function activates a port. For ports opened via the config files
|
|
* this is called just before entering the event loop. For ports create
|
|
* during runtime this is called when the RowStatus is set to Active or
|
|
* as second step for CreateAndGo.
|
|
*
|
|
* \param tp transport port
|
|
*
|
|
* \return SNMP error code
|
|
*/
|
|
static int
|
|
inet_activate(struct tport *tp)
|
|
{
|
|
struct inet_port *port = (struct inet_port *)tp;
|
|
|
|
return (port->activate(port));
|
|
}
|
|
|
|
/*
|
|
* Close the SNMP port if it is open and destroy it.
|
|
*
|
|
* \param tp port to close
|
|
*/
|
|
static void
|
|
inet_destroy_port(struct tport *tp)
|
|
{
|
|
struct inet_port *port = (struct inet_port *)tp;
|
|
|
|
deactivate_port(port);
|
|
|
|
trans_remove_port(tp);
|
|
|
|
free(port->dns_addr);
|
|
free(port);
|
|
}
|
|
|
|
/**
|
|
* If the input struct has no buffer allocated yet, do it now. If allocation
|
|
* fails, read the data into a local buffer and drop it.
|
|
*
|
|
* \param pi input struct
|
|
*
|
|
* \return -1 if allocation fails, 0 otherwise
|
|
*/
|
|
static int
|
|
inet_alloc_buf(struct port_input *pi)
|
|
{
|
|
char drop_buf[2000];
|
|
|
|
if (pi->buf == NULL) {
|
|
if ((pi->buf = buf_alloc(0)) == NULL) {
|
|
(void)recvfrom(pi->fd, drop_buf, sizeof(drop_buf),
|
|
0, NULL, NULL);
|
|
return (-1);
|
|
}
|
|
pi->buflen = buf_size(0);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Read message into input buffer. Get also the source address and any
|
|
* control data that is available. If the message is truncated, increment
|
|
* corresponding statistics.
|
|
*
|
|
* \param pi input object
|
|
* \param msg message object to fill
|
|
* \param cbuf control data buffer
|
|
*
|
|
* \return -1 when something goes wrong, 0 othersise
|
|
*/
|
|
static int
|
|
inet_read_msg(struct port_input *pi, struct msghdr *msg, char *cbuf)
|
|
{
|
|
struct iovec iov[1];
|
|
|
|
iov[0].iov_base = pi->buf;
|
|
iov[0].iov_len = pi->buflen;
|
|
|
|
msg->msg_name = pi->peer;
|
|
msg->msg_namelen = pi->peerlen;
|
|
msg->msg_iov = iov;
|
|
msg->msg_iovlen = 1;
|
|
msg->msg_control = cbuf;
|
|
msg->msg_controllen = RECV_CBUF_SIZE;
|
|
msg->msg_flags = 0;
|
|
|
|
memset(cbuf, 0, RECV_CBUF_SIZE);
|
|
|
|
const ssize_t len = recvmsg(pi->fd, msg, 0);
|
|
|
|
if (len == -1 || len == 0)
|
|
/* receive error */
|
|
return (-1);
|
|
|
|
if (msg->msg_flags & MSG_TRUNC) {
|
|
/* truncated - drop */
|
|
snmpd_stats.silentDrops++;
|
|
snmpd_stats.inTooLong++;
|
|
return (-1);
|
|
}
|
|
|
|
pi->length = (size_t)len;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Input available on socket.
|
|
*
|
|
* \param tp transport port
|
|
* \param pi input struct
|
|
*
|
|
* \return number of bytes received
|
|
*/
|
|
static ssize_t
|
|
inet_recv(struct tport *tp, struct port_input *pi)
|
|
{
|
|
struct inet_port *port = __containerof(tp, struct inet_port, tport);
|
|
struct port_sock *sock = __containerof(pi, struct port_sock, input);
|
|
|
|
assert(port->proto == BegemotSnmpdTransportProto_udp);
|
|
|
|
if (inet_alloc_buf(pi) == -1)
|
|
return (-1);
|
|
|
|
char cbuf[RECV_CBUF_SIZE];
|
|
struct msghdr msg;
|
|
|
|
if (inet_read_msg(pi, &msg, cbuf) == -1)
|
|
return (-1);
|
|
|
|
sock->parse_ctrl(sock, &msg);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Send message.
|
|
*
|
|
* \param tp port
|
|
* \param buf data to send
|
|
* \param len number of bytes to send
|
|
* \param addr destination address
|
|
* \param addlen destination address length
|
|
*
|
|
* \return number of bytes sent
|
|
*/
|
|
static ssize_t
|
|
inet_send2(struct tport *tp, const u_char *buf, size_t len,
|
|
struct port_input *pi)
|
|
{
|
|
struct inet_port *p = __containerof(tp, struct inet_port, tport);
|
|
struct port_sock *s = (pi == NULL) ? TAILQ_FIRST(&p->socks) :
|
|
__containerof(pi, struct port_sock, input);
|
|
|
|
struct iovec iov;
|
|
|
|
iov.iov_base = __DECONST(void*, buf);
|
|
iov.iov_len = len;
|
|
|
|
struct msghdr msg;
|
|
|
|
msg.msg_flags = 0;
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_name = (void *)pi->peer;
|
|
msg.msg_namelen = pi->peerlen;
|
|
|
|
char cbuf[XMIT_CBUF_SIZE];
|
|
if (s->set_ret_source) {
|
|
s->setsrc(s, &msg, cbuf);
|
|
} else {
|
|
msg.msg_control = NULL;
|
|
msg.msg_controllen = 0;
|
|
}
|
|
|
|
return (sendmsg(s->input.fd, &msg, 0));
|
|
}
|
|
|
|
/** exported to daemon */
|
|
const struct transport_def inet_trans = {
|
|
"inet",
|
|
OIDX_begemotSnmpdTransInet,
|
|
inet_start,
|
|
inet_stop,
|
|
inet_destroy_port,
|
|
inet_activate,
|
|
NULL,
|
|
inet_recv,
|
|
inet_send2,
|
|
};
|
|
|
|
struct inet_port_params {
|
|
/** index oid */
|
|
struct asn_oid index;
|
|
|
|
/** internet address type */
|
|
enum InetAddressType type;
|
|
|
|
/** internet address */
|
|
u_char *addr;
|
|
|
|
/** length of address */
|
|
size_t addr_len;
|
|
|
|
/** port number */
|
|
uint32_t port;
|
|
|
|
/** protocol */
|
|
enum BegemotSnmpdTransportProto proto;
|
|
};
|
|
|
|
/**
|
|
* IPv4 creation stuff. Parse the index, fill socket address and creates
|
|
* a port_sock.
|
|
*
|
|
* \param port the port to create
|
|
* \param params parameters from the SNMP SET
|
|
*
|
|
* \return SNMP error
|
|
*/
|
|
static int
|
|
ipv4_create(struct inet_port *port, struct inet_port_params *params)
|
|
{
|
|
|
|
if (params->addr_len != 4)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
|
|
struct port_sock *sock = calloc(1, sizeof(struct port_sock));
|
|
if (sock == NULL)
|
|
return (SNMP_ERR_GENERR);
|
|
|
|
snmpd_input_init(&sock->input);
|
|
|
|
TAILQ_INSERT_HEAD(&port->socks, sock, link);
|
|
|
|
struct sockaddr_in *sin =
|
|
(struct sockaddr_in *)&sock->bind_addr;
|
|
|
|
sin->sin_len = sizeof(struct sockaddr_in);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_port = htons(params->port);
|
|
memcpy(&sin->sin_addr, params->addr, 4); /* network byte order */
|
|
|
|
sock->port = port;
|
|
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
|
|
/*
|
|
* An IPv4 inet port is ready. Delegate to the generic code to read the data
|
|
* and react.
|
|
*
|
|
* \param fd file descriptor that is ready
|
|
* \param udata inet_port pointer
|
|
*/
|
|
static void
|
|
ipv4_input(int fd __unused, void *udata)
|
|
{
|
|
struct port_sock *sock = udata;
|
|
|
|
sock->input.peerlen = sizeof(struct sockaddr_in);
|
|
snmpd_input(&sock->input, &sock->port->tport);
|
|
}
|
|
|
|
/**
|
|
* Activate an IPv4 socket.
|
|
*
|
|
* \param sock thhe socket to activate
|
|
*
|
|
* \return error code
|
|
*/
|
|
static int
|
|
ipv4_activate_sock(struct port_sock *sock)
|
|
{
|
|
if ((sock->input.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
|
|
syslog(LOG_ERR, "creating UDP4 socket: %m");
|
|
return (SNMP_ERR_RES_UNAVAIL);
|
|
}
|
|
|
|
const struct sockaddr_in *sin =
|
|
(const struct sockaddr_in *)&sock->bind_addr;
|
|
|
|
if (sin->sin_addr.s_addr == INADDR_ANY) {
|
|
/* need to know from which address to return */
|
|
static const int on = 1;
|
|
|
|
if (setsockopt(sock->input.fd, IPPROTO_IP, IP_RECVDSTADDR, &on,
|
|
sizeof(on)) == -1) {
|
|
syslog(LOG_ERR, "setsockopt(IP_RECVDSTADDR): %m");
|
|
(void)close(sock->input.fd);
|
|
sock->input.fd = -1;
|
|
return (SNMP_ERR_GENERR);
|
|
}
|
|
sock->set_ret_source = true;
|
|
}
|
|
|
|
if (bind(sock->input.fd, (const struct sockaddr *)sin, sizeof(*sin))) {
|
|
if (errno == EADDRNOTAVAIL) {
|
|
(void)close(sock->input.fd);
|
|
sock->input.fd = -1;
|
|
return (SNMP_ERR_INCONS_NAME);
|
|
}
|
|
syslog(LOG_ERR, "bind: %s:%u %m", inet_ntoa(sin->sin_addr),
|
|
ntohs(sin->sin_port));
|
|
(void)close(sock->input.fd);
|
|
sock->input.fd = -1;
|
|
return (SNMP_ERR_GENERR);
|
|
}
|
|
|
|
if ((sock->input.id = fd_select(sock->input.fd, ipv4_input,
|
|
sock, NULL)) == NULL) {
|
|
(void)close(sock->input.fd);
|
|
sock->input.fd = -1;
|
|
return (SNMP_ERR_GENERR);
|
|
}
|
|
sock->input.peer = (struct sockaddr *)&sock->ret_dest;
|
|
|
|
sock->parse_ctrl = ipv4_parse_ctrl;
|
|
sock->setsrc = ipv4_setsrc;
|
|
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
|
|
/**
|
|
* Open an IPv4 socket. Make the socket, bind it and put it on the select
|
|
* list. The socket struct has already been created during creation.
|
|
*
|
|
* \param p inet port
|
|
*
|
|
* \return SNMP error code
|
|
*/
|
|
static int
|
|
ipv4_activate(struct inet_port *p)
|
|
{
|
|
struct port_sock *sock = TAILQ_FIRST(&p->socks);
|
|
assert(sock);
|
|
assert(!TAILQ_NEXT(sock, link));
|
|
|
|
const int ret = ipv4_activate_sock(sock);
|
|
if (ret == SNMP_ERR_NOERROR)
|
|
p->row_status = RowStatus_active;
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* Close an IPv4 socket. Keep the sock object.
|
|
*
|
|
* \param p inet port
|
|
*/
|
|
static void
|
|
ipv4_deactivate(struct inet_port *p)
|
|
{
|
|
struct port_sock *sock = TAILQ_FIRST(&p->socks);
|
|
assert(sock);
|
|
assert(!TAILQ_NEXT(sock, link));
|
|
|
|
snmpd_input_close(&sock->input);
|
|
|
|
p->row_status = RowStatus_notInService;
|
|
}
|
|
|
|
/**
|
|
* Parse the control data received with a UDPv4 packet. This may contain
|
|
* credentials (for a local connection) and the address of the interface
|
|
* the message was received on. If there are credentials set the priv flag
|
|
* if the effective UID is zero.
|
|
*
|
|
* \param sock the sock object
|
|
* \param msg the received message
|
|
*/
|
|
static void
|
|
ipv4_parse_ctrl(struct port_sock *sock, const struct msghdr *msg)
|
|
{
|
|
struct sockcred *cred = NULL;
|
|
|
|
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
|
|
cmsg = CMSG_NXTHDR(msg, cmsg)) {
|
|
|
|
if (cmsg->cmsg_level == IPPROTO_IP &&
|
|
cmsg->cmsg_type == IP_RECVDSTADDR) {
|
|
memcpy(&sock->ret_source.a4, CMSG_DATA(cmsg),
|
|
sizeof(struct in_addr));
|
|
|
|
} else if (cmsg->cmsg_level == SOL_SOCKET &&
|
|
cmsg->cmsg_type == SCM_CREDS) {
|
|
cred = (struct sockcred *)(void *)CMSG_DATA(cmsg);
|
|
}
|
|
}
|
|
|
|
sock->input.priv = 0;
|
|
if (sock->input.cred && cred)
|
|
/* remote end has sent credentials */
|
|
sock->input.priv = (cred->sc_euid == 0);
|
|
}
|
|
|
|
/**
|
|
* Set the source address option for IPv4 sockets.
|
|
*
|
|
* \param sock socket object
|
|
* \param msg message
|
|
*/
|
|
static void
|
|
ipv4_setsrc(struct port_sock *sock, struct msghdr *msg, char *cbuf)
|
|
{
|
|
struct cmsghdr *cmsg;
|
|
|
|
msg->msg_control = cbuf;
|
|
msg->msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
|
|
|
|
/* select outgoing interface by setting source address */
|
|
cmsg = CMSG_FIRSTHDR(msg);
|
|
cmsg->cmsg_level = IPPROTO_IP;
|
|
cmsg->cmsg_type = IP_SENDSRCADDR;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
|
|
memcpy(CMSG_DATA(cmsg), &sock->ret_source.a4,
|
|
sizeof(struct in_addr));
|
|
}
|
|
|
|
/**
|
|
* Common part of IPv6 creation. This is used by both ipv6_create() and
|
|
* ipv6z_create().
|
|
*
|
|
* \param port the table row
|
|
* \param params creation parameters
|
|
* \param scope_id scope_id (0 or from index)
|
|
*
|
|
* \return SNMP_ERR_NOERROR if port has been created, error code otherwise
|
|
*/
|
|
static int
|
|
ipv6_create_common(struct inet_port *port, struct inet_port_params *params,
|
|
u_int scope_id)
|
|
{
|
|
struct port_sock *sock = calloc(1, sizeof(struct port_sock));
|
|
|
|
if (sock == NULL)
|
|
return (SNMP_ERR_GENERR);
|
|
|
|
struct sockaddr_in6 *sin = (struct sockaddr_in6 *)&sock->bind_addr;
|
|
|
|
sin->sin6_len = sizeof(struct sockaddr_in6);
|
|
sin->sin6_family = AF_INET6;
|
|
sin->sin6_port = htons(params->port);
|
|
sin->sin6_flowinfo = 0;
|
|
sin->sin6_scope_id = scope_id;
|
|
|
|
memcpy(sin->sin6_addr.s6_addr, params->addr, 16);
|
|
|
|
if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr) && scope_id == 0) {
|
|
char buf[INET6_ADDRSTRLEN];
|
|
syslog(LOG_INFO, "%s: link local address used without scope "
|
|
"index: %s", __func__, inet_ntop(AF_INET6,
|
|
&sin->sin6_addr, buf, sizeof(buf)));
|
|
free(sock);
|
|
return (SNMP_ERR_NO_CREATION);
|
|
}
|
|
|
|
sock->port = port;
|
|
|
|
snmpd_input_init(&sock->input);
|
|
TAILQ_INSERT_HEAD(&port->socks, sock, link);
|
|
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
|
|
/**
|
|
* IPv6 creation stuff. Parse the index, fill socket address and creates
|
|
* a port_sock.
|
|
*
|
|
* \param port the port to create
|
|
* \param params parameters from the SNMP SET
|
|
*
|
|
* \return SNMP error
|
|
*/
|
|
static int
|
|
ipv6_create(struct inet_port *port, struct inet_port_params *params)
|
|
{
|
|
if (params->addr_len != 16)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
|
|
const int ret = ipv6_create_common(port, params, 0);
|
|
if (ret != SNMP_ERR_NOERROR)
|
|
return (ret);
|
|
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
|
|
/*
|
|
* An IPv6 inet port is ready. Delegate to the generic code to read the data
|
|
* and react.
|
|
*
|
|
* \param fd file descriptor that is ready
|
|
* \param udata inet_port pointer
|
|
*/
|
|
static void
|
|
ipv6_input(int fd __unused, void *udata)
|
|
{
|
|
struct port_sock *sock = udata;
|
|
|
|
sock->input.peerlen = sizeof(struct sockaddr_in6);
|
|
snmpd_input(&sock->input, &sock->port->tport);
|
|
}
|
|
|
|
/**
|
|
* Activate an IPv6 socket.
|
|
*
|
|
* \param sock thhe socket to activate
|
|
*
|
|
* \return error code
|
|
*/
|
|
static int
|
|
ipv6_activate_sock(struct port_sock *sock)
|
|
{
|
|
if ((sock->input.fd = socket(PF_INET6, SOCK_DGRAM, 0)) == -1) {
|
|
syslog(LOG_ERR, "creating UDP6 socket: %m");
|
|
return (SNMP_ERR_RES_UNAVAIL);
|
|
}
|
|
|
|
const struct sockaddr_in6 *sin =
|
|
(const struct sockaddr_in6 *)&sock->bind_addr;
|
|
|
|
if (memcmp(&sin->sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0) {
|
|
/* need to know from which address to return */
|
|
static const int on = 1;
|
|
|
|
if (setsockopt(sock->input.fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
|
|
&on, sizeof(on)) == -1) {
|
|
syslog(LOG_ERR, "setsockopt(IP6_PKTINFO): %m");
|
|
(void)close(sock->input.fd);
|
|
sock->input.fd = -1;
|
|
return (SNMP_ERR_GENERR);
|
|
}
|
|
sock->set_ret_source = true;
|
|
}
|
|
|
|
if (bind(sock->input.fd, (const struct sockaddr *)sin, sizeof(*sin))) {
|
|
if (community != COMM_INITIALIZE && errno == EADDRNOTAVAIL) {
|
|
(void)close(sock->input.fd);
|
|
sock->input.fd = -1;
|
|
return (SNMP_ERR_INCONS_NAME);
|
|
}
|
|
char buf[INET6_ADDRSTRLEN];
|
|
syslog(LOG_ERR, "bind: %s:%u:%u %m", inet_ntop(AF_INET6,
|
|
&sin->sin6_addr, buf, sizeof(buf)), sin->sin6_scope_id,
|
|
ntohs(sin->sin6_port));
|
|
(void)close(sock->input.fd);
|
|
sock->input.fd = -1;
|
|
return (SNMP_ERR_GENERR);
|
|
}
|
|
if ((sock->input.id = fd_select(sock->input.fd, ipv6_input,
|
|
sock, NULL)) == NULL) {
|
|
(void)close(sock->input.fd);
|
|
sock->input.fd = -1;
|
|
return (SNMP_ERR_GENERR);
|
|
}
|
|
sock->input.peer = (struct sockaddr *)&sock->ret_dest;
|
|
|
|
sock->parse_ctrl = ipv6_parse_ctrl;
|
|
sock->setsrc = ipv6_setsrc;
|
|
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
|
|
/**
|
|
* Open an IPv6 socket.
|
|
*
|
|
* \param port inet port
|
|
*
|
|
* \return SNMP error code
|
|
*/
|
|
static int
|
|
ipv6_activate(struct inet_port *p)
|
|
{
|
|
struct port_sock *sock = TAILQ_FIRST(&p->socks);
|
|
assert(sock);
|
|
|
|
const int ret = ipv6_activate_sock(sock);
|
|
|
|
if (ret == SNMP_ERR_NOERROR)
|
|
p->row_status = RowStatus_active;
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* Close an IPv6 socket. Keep the sock object.
|
|
*
|
|
* \param p inet port
|
|
*/
|
|
static void
|
|
ipv6_deactivate(struct inet_port *p)
|
|
{
|
|
struct port_sock *sock = TAILQ_FIRST(&p->socks);
|
|
assert(sock);
|
|
assert(!TAILQ_NEXT(sock, link));
|
|
|
|
snmpd_input_close(&sock->input);
|
|
|
|
p->row_status = RowStatus_notInService;
|
|
}
|
|
|
|
/**
|
|
* IPv6 specific part of message processing. The control data may contain
|
|
* credentials and packet info that contains the destination address of
|
|
* the packet.
|
|
*
|
|
* \param sock the sock object
|
|
* \param msg the received message
|
|
*/
|
|
static void
|
|
ipv6_parse_ctrl(struct port_sock *sock, const struct msghdr *msg)
|
|
{
|
|
struct sockcred *cred = NULL;
|
|
|
|
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
|
|
cmsg = CMSG_NXTHDR(msg, cmsg)) {
|
|
|
|
if (cmsg->cmsg_level == IPPROTO_IPV6 &&
|
|
cmsg->cmsg_type == IPV6_PKTINFO) {
|
|
const struct in6_pktinfo *info =
|
|
(const struct in6_pktinfo *)(const void *)
|
|
CMSG_DATA(cmsg);
|
|
sock->ret_source.a6.ipi6_addr = info->ipi6_addr;
|
|
sock->ret_source.a6.ipi6_ifindex =
|
|
!IN6_IS_ADDR_LINKLOCAL(&info->ipi6_addr) ? 0:
|
|
info->ipi6_ifindex;
|
|
|
|
} else if (cmsg->cmsg_level == SOL_SOCKET &&
|
|
cmsg->cmsg_type == SCM_CREDS) {
|
|
cred = (struct sockcred *)(void *)CMSG_DATA(cmsg);
|
|
}
|
|
}
|
|
|
|
sock->input.priv = 0;
|
|
if (sock->input.cred && cred)
|
|
/* remote end has sent credentials */
|
|
sock->input.priv = (cred->sc_euid == 0);
|
|
}
|
|
|
|
/**
|
|
* Set the source address option for IPv4 sockets.
|
|
*
|
|
* \param sock socket object
|
|
* \param msg message
|
|
*/
|
|
static void
|
|
ipv6_setsrc(struct port_sock *sock, struct msghdr *msg, char *cbuf)
|
|
{
|
|
struct cmsghdr *cmsg;
|
|
|
|
msg->msg_control = cbuf;
|
|
msg->msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
|
|
|
|
/* select outgoing interface by setting source address */
|
|
cmsg = CMSG_FIRSTHDR(msg);
|
|
cmsg->cmsg_level = IPPROTO_IPV6;
|
|
cmsg->cmsg_type = IPV6_PKTINFO;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
|
|
memcpy(CMSG_DATA(cmsg), &sock->ret_source.a6,
|
|
sizeof(struct in6_pktinfo));
|
|
}
|
|
|
|
/**
|
|
* IPv6z creation stuff. Parse the index, fill socket address and creates
|
|
* a port_sock.
|
|
*
|
|
* \param port the port to create
|
|
* \param params parameters from the SNMP SET
|
|
*
|
|
* \return SNMP error
|
|
*/
|
|
static int
|
|
ipv6z_create(struct inet_port *port, struct inet_port_params *params)
|
|
{
|
|
if (params->addr_len != 20)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
|
|
u_int scope_id = 0;
|
|
for (u_int i = 16; i < 20; i++) {
|
|
scope_id <<= 8;
|
|
scope_id |= params->addr[i];
|
|
}
|
|
|
|
const int ret = ipv6_create_common(port, params, scope_id);
|
|
if (ret != SNMP_ERR_NOERROR)
|
|
return (ret);
|
|
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
|
|
/**
|
|
* DNS name creation stuff. Parse the index and save the string.
|
|
* This does not create a socket struct.
|
|
*
|
|
* \param port the port to create
|
|
* \param params parameters from the SNMP SET
|
|
*
|
|
* \return SNMP error
|
|
*/
|
|
static int
|
|
dns_create(struct inet_port *port, struct inet_port_params *params)
|
|
{
|
|
if (params->addr_len > 64)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
|
|
if (strnlen(params->addr, params->addr_len) !=
|
|
params->addr_len)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
|
|
if ((port->dns_addr = realloc(params->addr,
|
|
params->addr_len + 1)) == NULL)
|
|
return (SNMP_ERR_GENERR);
|
|
|
|
port->dns_addr[params->addr_len] = '\0';
|
|
params->addr = NULL;
|
|
|
|
port->dns_port = htons(params->port);
|
|
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
|
|
/**
|
|
* Open sockets. This loops through all the addresses returned by getaddrinfo
|
|
* and opens a socket for each of them.
|
|
*
|
|
* \param port inet port
|
|
*
|
|
* \return SNMP error code
|
|
*/
|
|
static int
|
|
dns_activate(struct inet_port *port)
|
|
{
|
|
struct addrinfo hints;
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_DGRAM; // XXX udp-only
|
|
hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE | AI_NUMERICSERV;
|
|
|
|
char portbuf[8];
|
|
sprintf(portbuf, "%hu", ntohs(port->dns_port));
|
|
|
|
struct addrinfo *res0;
|
|
int error = getaddrinfo(port->dns_addr[0] == '\0'
|
|
? NULL : port->dns_addr,
|
|
portbuf, &hints, &res0);
|
|
|
|
if (error) {
|
|
syslog(LOG_ERR, "cannot resolve address '%s': %s",
|
|
port->dns_addr, gai_strerror(error));
|
|
return (SNMP_ERR_GENERR);
|
|
}
|
|
|
|
for (struct addrinfo *res = res0; res != NULL; res = res->ai_next) {
|
|
if (res->ai_family != AF_INET && res->ai_family != AF_INET6)
|
|
continue;
|
|
|
|
struct port_sock *sock = calloc(1, sizeof(struct port_sock));
|
|
if (sock == NULL)
|
|
return (SNMP_ERR_GENERR);
|
|
|
|
snmpd_input_init(&sock->input);
|
|
sock->port = port;
|
|
|
|
int ret = SNMP_ERR_NOERROR;
|
|
|
|
if (res->ai_family == AF_INET) {
|
|
*(struct sockaddr_in *)&sock->bind_addr =
|
|
*(struct sockaddr_in *)(void *)res->ai_addr;
|
|
ret = ipv4_activate_sock(sock);
|
|
} else {
|
|
*(struct sockaddr_in6 *)&sock->bind_addr =
|
|
*(struct sockaddr_in6 *)(void *)res->ai_addr;
|
|
ret = ipv6_activate_sock(sock);
|
|
}
|
|
|
|
if (ret != SNMP_ERR_NOERROR)
|
|
free(sock);
|
|
else
|
|
TAILQ_INSERT_HEAD(&port->socks, sock, link);
|
|
}
|
|
|
|
if (!TAILQ_EMPTY(&port->socks))
|
|
port->row_status = RowStatus_active;
|
|
|
|
freeaddrinfo(res0);
|
|
return (SNMP_ERR_GENERR);
|
|
}
|
|
|
|
/**
|
|
* Deactive the socket. Close all open sockets and delete all sock objects.
|
|
*
|
|
* \param port inet port
|
|
*/
|
|
static void
|
|
dns_deactivate(struct inet_port *port)
|
|
{
|
|
while (!TAILQ_EMPTY(&port->socks)) {
|
|
struct port_sock *sock = TAILQ_FIRST(&port->socks);
|
|
TAILQ_REMOVE(&port->socks, sock, link);
|
|
snmpd_input_close(&sock->input);
|
|
free(sock);
|
|
}
|
|
port->row_status = RowStatus_notInService;
|
|
}
|
|
|
|
static int
|
|
inet_create(struct inet_port_params *params, struct inet_port **pp)
|
|
{
|
|
int err = SNMP_ERR_NOERROR;
|
|
struct inet_port *port = NULL;
|
|
|
|
if (params->port > 0xffff) {
|
|
err = SNMP_ERR_NO_CREATION;
|
|
goto fail;
|
|
}
|
|
|
|
if ((port = malloc(sizeof(*port))) == NULL) {
|
|
err = SNMP_ERR_GENERR;
|
|
goto fail;
|
|
}
|
|
memset(port, 0, sizeof(*port));
|
|
TAILQ_INIT(&port->socks);
|
|
|
|
port->proto = params->proto;
|
|
port->tport.index = params->index;
|
|
|
|
switch (params->type) {
|
|
|
|
case InetAddressType_ipv4:
|
|
port->create = ipv4_create;
|
|
port->activate = ipv4_activate;
|
|
port->deactivate = ipv4_deactivate;
|
|
break;
|
|
|
|
case InetAddressType_ipv6:
|
|
port->create = ipv6_create;
|
|
port->activate = ipv6_activate;
|
|
port->deactivate = ipv6_deactivate;
|
|
break;
|
|
|
|
case InetAddressType_ipv6z:
|
|
port->create = ipv6z_create;
|
|
port->activate = ipv6_activate;
|
|
port->deactivate = ipv6_deactivate;
|
|
break;
|
|
|
|
case InetAddressType_dns:
|
|
port->create = dns_create;
|
|
port->activate = dns_activate;
|
|
port->deactivate = dns_deactivate;
|
|
break;
|
|
|
|
default:
|
|
err = SNMP_ERR_NO_CREATION;
|
|
goto fail;
|
|
}
|
|
|
|
if ((err = port->create(port, params)) != SNMP_ERR_NOERROR)
|
|
goto fail;
|
|
|
|
*pp = port;
|
|
trans_insert_port(my_trans, &port->tport);
|
|
return (err);
|
|
|
|
fail:
|
|
free(port->dns_addr);
|
|
free(port);
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
create_and_go(struct snmp_context *ctx, struct inet_port_params *params)
|
|
{
|
|
int err;
|
|
struct inet_port *port;
|
|
|
|
if ((err = inet_create(params, &port)) != SNMP_ERR_NOERROR)
|
|
return (err);
|
|
|
|
port->row_status = RowStatus_createAndGo;
|
|
ctx->scratch->ptr1 = port;
|
|
|
|
if (community == COMM_INITIALIZE)
|
|
return (err);
|
|
|
|
return (inet_activate(&port->tport));
|
|
}
|
|
|
|
static int
|
|
create_and_wait(struct snmp_context *ctx, struct inet_port_params *params)
|
|
{
|
|
int err;
|
|
struct inet_port *port;
|
|
|
|
if ((err = inet_create(params, &port)) != SNMP_ERR_NOERROR)
|
|
return (err);
|
|
|
|
port->row_status = RowStatus_createAndWait;
|
|
ctx->scratch->ptr1 = port;
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* This is called to set a RowStatus value in the port table during
|
|
* SET processing.
|
|
*
|
|
* When this is called during initialization time and the RowStatus is set
|
|
* to CreateAndGo, the port is actually not activated. This is done when
|
|
* the main code calls the init() for all ports later.
|
|
*/
|
|
static int
|
|
inet_port_set(struct snmp_context *ctx, struct inet_port *port,
|
|
struct inet_port_params *params, enum RowStatus nstatus)
|
|
{
|
|
switch (nstatus) {
|
|
|
|
case RowStatus_createAndGo:
|
|
if (port != NULL)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
ctx->scratch->int1 = SET_CREATED;
|
|
return (create_and_go(ctx, params));
|
|
|
|
case RowStatus_createAndWait:
|
|
if (port != NULL)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
ctx->scratch->int1 = SET_CREATED;
|
|
return (create_and_wait(ctx, params));
|
|
|
|
case RowStatus_active:
|
|
if (port == NULL)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
|
|
switch (port->row_status) {
|
|
|
|
case RowStatus_notReady:
|
|
/* this can not happend */
|
|
abort();
|
|
|
|
case RowStatus_notInService:
|
|
ctx->scratch->int1 = SET_ACTIVATED;
|
|
return (inet_activate(&port->tport));
|
|
|
|
case RowStatus_active:
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case RowStatus_createAndGo:
|
|
case RowStatus_createAndWait:
|
|
case RowStatus_destroy:
|
|
abort();
|
|
}
|
|
break;
|
|
|
|
case RowStatus_notInService:
|
|
if (port == NULL)
|
|
return (SNMP_ERR_INCONS_VALUE);
|
|
|
|
switch (port->row_status) {
|
|
|
|
case RowStatus_notReady:
|
|
/* this can not happend */
|
|
abort();
|
|
|
|
case RowStatus_notInService:
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case RowStatus_active:
|
|
/* this is done during commit */
|
|
ctx->scratch->int1 = SET_DEACTIVATE;
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case RowStatus_createAndGo:
|
|
case RowStatus_createAndWait:
|
|
case RowStatus_destroy:
|
|
abort();
|
|
}
|
|
break;
|
|
|
|
case RowStatus_destroy:
|
|
/* this is done during commit */
|
|
ctx->scratch->int1 = SET_DESTROY;
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case RowStatus_notReady:
|
|
return (SNMP_ERR_WRONG_VALUE);
|
|
}
|
|
abort();
|
|
}
|
|
|
|
/*
|
|
* Port table
|
|
*/
|
|
int
|
|
op_snmp_trans_inet(struct snmp_context *ctx, struct snmp_value *value,
|
|
u_int sub, u_int iidx __unused, enum snmp_op op)
|
|
{
|
|
asn_subid_t which = value->var.subs[sub - 1];
|
|
struct inet_port *port;
|
|
struct inet_port_params params;
|
|
int ret;
|
|
|
|
switch (op) {
|
|
|
|
case SNMP_OP_GETNEXT:
|
|
if ((port = (struct inet_port *)trans_next_port(my_trans,
|
|
&value->var, sub)) == NULL)
|
|
return (SNMP_ERR_NOSUCHNAME);
|
|
index_append(&value->var, sub, &port->tport.index);
|
|
goto fetch;
|
|
|
|
case SNMP_OP_GET:
|
|
if ((port = (struct inet_port *)trans_find_port(my_trans,
|
|
&value->var, sub)) == NULL)
|
|
return (SNMP_ERR_NOSUCHNAME);
|
|
goto fetch;
|
|
|
|
case SNMP_OP_SET:
|
|
port = (struct inet_port *)trans_find_port(my_trans,
|
|
&value->var, sub);
|
|
|
|
if (which != LEAF_begemotSnmpdTransInetStatus)
|
|
abort();
|
|
if (!isok_RowStatus(value->v.integer))
|
|
return (SNMP_ERR_WRONG_VALUE);
|
|
|
|
if (index_decode(&value->var, sub, iidx, ¶ms.type,
|
|
¶ms.addr, ¶ms.addr_len, ¶ms.port,
|
|
¶ms.proto))
|
|
return (SNMP_ERR_NO_CREATION);
|
|
|
|
asn_slice_oid(¶ms.index, &value->var, sub, value->var.len);
|
|
|
|
ret = inet_port_set(ctx, port, ¶ms,
|
|
(enum RowStatus)value->v.integer);
|
|
|
|
free(params.addr);
|
|
return (ret);
|
|
|
|
case SNMP_OP_ROLLBACK:
|
|
if ((port = (struct inet_port *)trans_find_port(my_trans,
|
|
&value->var, sub)) == NULL)
|
|
/* cannot happen */
|
|
abort();
|
|
|
|
switch (ctx->scratch->int1) {
|
|
|
|
case SET_CREATED:
|
|
/* newly created */
|
|
assert(port != NULL);
|
|
inet_destroy_port(&port->tport);
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case SET_DESTROY:
|
|
/* do it now */
|
|
assert(port != NULL);
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case SET_ACTIVATED:
|
|
deactivate_port(port);
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case SET_DEACTIVATE:
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
abort();
|
|
|
|
case SNMP_OP_COMMIT:
|
|
if ((port = (struct inet_port *)trans_find_port(my_trans,
|
|
&value->var, sub)) == NULL)
|
|
/* cannot happen */
|
|
abort();
|
|
|
|
switch (ctx->scratch->int1) {
|
|
|
|
case SET_CREATED:
|
|
/* newly created */
|
|
assert(port != NULL);
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case SET_DESTROY:
|
|
/* do it now */
|
|
assert(port != NULL);
|
|
inet_destroy_port(&port->tport);
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case SET_ACTIVATED:
|
|
return (SNMP_ERR_NOERROR);
|
|
|
|
case SET_DEACTIVATE:
|
|
deactivate_port(port);
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|
|
abort();
|
|
}
|
|
abort();
|
|
|
|
fetch:
|
|
switch (which) {
|
|
|
|
case LEAF_begemotSnmpdTransInetStatus:
|
|
value->v.integer = port->row_status;
|
|
break;
|
|
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
return (SNMP_ERR_NOERROR);
|
|
}
|