phunix/minix/net/lwip/udpsock.c
David van Moolenbroek ef8d499e2d Add lwip: a new lwIP-based TCP/IP service
This commit adds a new TCP/IP service to MINIX 3.  As its core, the
service uses the lwIP TCP/IP stack for maintenance reasons.  The
service aims to be compatible with NetBSD userland, including its
low-level network management utilities.  It also aims to support
modern features such as IPv6.  In summary, the new LWIP service has
support for the following main features:

- TCP, UDP, RAW sockets with mostly standard BSD API semantics;
- IPv6 support: host mode (complete) and router mode (partial);
- most of the standard BSD API socket options (SO_);
- all of the standard BSD API message flags (MSG_);
- the most used protocol-specific socket and control options;
- a default loopback interface and the ability to create one more;
- configuration-free ethernet interfaces and driver tracking;
- queuing and multiple concurrent requests to each ethernet driver;
- standard ioctl(2)-based BSD interface management;
- radix tree backed, destination-based routing;
- routing sockets for standard BSD route reporting and management;
- multicast traffic and multicast group membership tracking;
- Berkeley Packet Filter (BPF) devices;
- standard and custom sysctl(7) nodes for many internals;
- a slab allocation based, hybrid static/dynamic memory pool model.

Many of its modules come with fairly elaborate comments that cover
many aspects of what is going on.  The service is primarily a socket
driver built on top of the libsockdriver library, but for BPF devices
it is at the same time also a character driver.

Change-Id: Ib0c02736234b21143915e5fcc0fda8fe408f046f
2017-04-30 13:16:03 +00:00

998 lines
26 KiB
C

/* LWIP service - udpsock.c - UDP sockets */
#include "lwip.h"
#include "ifaddr.h"
#include "pktsock.h"
#include "lwip/udp.h"
#include <netinet/udp.h>
#include <netinet/ip_var.h>
#include <netinet/udp_var.h>
/* The number of UDP sockets. Inherited from the lwIP configuration. */
#define NR_UDPSOCK MEMP_NUM_UDP_PCB
/*
* Outgoing packets are not getting buffered, so the send buffer size simply
* determines the maximum size for sent packets. The send buffer maximum is
* therefore limited to the maximum size of a single packet (64K-1 bytes),
* which is already enforced by lwIP's 16-bit length parameter to pbuf_alloc().
*
* The actual transmission may enforce a lower limit, though. The full packet
* size must not exceed the same 64K-1 limit, and that includes any headers
* that still have to be prepended to the given packet. The size of those
* headers depends on the socket type (IPv4/IPv6) and the IP_HDRINCL setting.
*/
#define UDP_MAX_PAYLOAD (UINT16_MAX)
#define UDP_SNDBUF_MIN 1 /* minimum UDP send buffer size */
#define UDP_SNDBUF_DEF 8192 /* default UDP send buffer size */
#define UDP_SNDBUF_MAX UDP_MAX_PAYLOAD /* maximum UDP send buffer size */
#define UDP_RCVBUF_MIN MEMPOOL_BUFSIZE /* minimum UDP receive buffer size */
#define UDP_RCVBUF_DEF 32768 /* default UDP receive buffer size */
#define UDP_RCVBUF_MAX 65536 /* maximum UDP receive buffer size */
static struct udpsock {
struct pktsock udp_pktsock; /* pkt socket, MUST be first */
struct udp_pcb *udp_pcb; /* lwIP UDP control block */
SIMPLEQ_ENTRY(udpsock) udp_next; /* next in free list */
} udp_array[NR_UDPSOCK];
static SIMPLEQ_HEAD(, udpsock) udp_freelist; /* list of free UDP sockets */
static const struct sockevent_ops udpsock_ops;
#define udpsock_get_sock(udp) (ipsock_get_sock(udpsock_get_ipsock(udp)))
#define udpsock_get_ipsock(udp) (pktsock_get_ipsock(&(udp)->udp_pktsock))
#define udpsock_is_ipv6(udp) (ipsock_is_ipv6(udpsock_get_ipsock(udp)))
#define udpsock_is_conn(udp) \
(udp_flags((udp)->udp_pcb) & UDP_FLAGS_CONNECTED)
static ssize_t udpsock_pcblist(struct rmib_call *, struct rmib_node *,
struct rmib_oldp *, struct rmib_newp *);
/* The CTL_NET {PF_INET,PF_INET6} IPPROTO_UDP subtree. */
/* TODO: add many more and make some of them writable.. */
static struct rmib_node net_inet_udp_table[] = {
/* 1*/ [UDPCTL_CHECKSUM] = RMIB_INT(RMIB_RO, 1, "checksum",
"Compute UDP checksums"),
/* 2*/ [UDPCTL_SENDSPACE] = RMIB_INT(RMIB_RO, UDP_SNDBUF_DEF,
"sendspace",
"Default UDP send buffer size"),
/* 3*/ [UDPCTL_RECVSPACE] = RMIB_INT(RMIB_RO, UDP_RCVBUF_DEF,
"recvspace",
"Default UDP receive buffer size"),
/* 4*/ [UDPCTL_LOOPBACKCKSUM] = RMIB_FUNC(RMIB_RW | CTLTYPE_INT, sizeof(int),
loopif_cksum, "do_loopback_cksum",
"Perform UDP checksum on loopback"),
/*+0*/ [UDPCTL_MAXID] = RMIB_FUNC(RMIB_RO | CTLTYPE_NODE, 0,
udpsock_pcblist, "pcblist",
"UDP protocol control block list"),
};
static struct rmib_node net_inet_udp_node =
RMIB_NODE(RMIB_RO, net_inet_udp_table, "udp", "UDPv4 related settings");
static struct rmib_node net_inet6_udp6_node =
RMIB_NODE(RMIB_RO, net_inet_udp_table, "udp6", "UDPv6 related settings");
/*
* Initialize the UDP sockets module.
*/
void
udpsock_init(void)
{
unsigned int slot;
/* Initialize the list of free UDP sockets. */
SIMPLEQ_INIT(&udp_freelist);
for (slot = 0; slot < __arraycount(udp_array); slot++)
SIMPLEQ_INSERT_TAIL(&udp_freelist, &udp_array[slot], udp_next);
/* Register the net.inet.udp and net.inet6.udp6 RMIB subtrees. */
mibtree_register_inet(PF_INET, IPPROTO_UDP, &net_inet_udp_node);
mibtree_register_inet(PF_INET6, IPPROTO_UDP, &net_inet6_udp6_node);
}
/*
* A packet has arrived on a UDP socket. We own the given packet buffer, and
* so we must free it if we do not want to keep it.
*/
static void
udpsock_input(void * arg, struct udp_pcb * pcb __unused, struct pbuf * pbuf,
const ip_addr_t * ipaddr, uint16_t port)
{
struct udpsock *udp = (struct udpsock *)arg;
/* All UDP input processing is handled by pktsock. */
pktsock_input(&udp->udp_pktsock, pbuf, ipaddr, port);
}
/*
* Create a UDP socket.
*/
sockid_t
udpsock_socket(int domain, int protocol, struct sock ** sockp,
const struct sockevent_ops ** ops)
{
struct udpsock *udp;
unsigned int flags;
uint8_t ip_type;
switch (protocol) {
case 0:
case IPPROTO_UDP:
break;
/* NetBSD does not support IPPROTO_UDPLITE, even though lwIP does. */
default:
return EPROTONOSUPPORT;
}
if (SIMPLEQ_EMPTY(&udp_freelist))
return ENOBUFS;
udp = SIMPLEQ_FIRST(&udp_freelist);
ip_type = pktsock_socket(&udp->udp_pktsock, domain, UDP_SNDBUF_DEF,
UDP_RCVBUF_DEF, sockp);
/* We should have enough PCBs so this call should not fail.. */
if ((udp->udp_pcb = udp_new_ip_type(ip_type)) == NULL)
return ENOBUFS;
udp_recv(udp->udp_pcb, udpsock_input, (void *)udp);
/* By default, the multicast TTL is 1 and looping is enabled. */
udp_set_multicast_ttl(udp->udp_pcb, 1);
flags = udp_flags(udp->udp_pcb);
udp_setflags(udp->udp_pcb, flags | UDP_FLAGS_MULTICAST_LOOP);
SIMPLEQ_REMOVE_HEAD(&udp_freelist, udp_next);
*ops = &udpsock_ops;
return SOCKID_UDP | (sockid_t)(udp - udp_array);
}
/*
* Bind a UDP socket to a local address.
*/
static int
udpsock_bind(struct sock * sock, const struct sockaddr * addr,
socklen_t addr_len, endpoint_t user_endpt)
{
struct udpsock *udp = (struct udpsock *)sock;
ip_addr_t ipaddr;
uint16_t port;
err_t err;
int r;
if ((r = ipsock_get_src_addr(udpsock_get_ipsock(udp), addr, addr_len,
user_endpt, &udp->udp_pcb->local_ip, udp->udp_pcb->local_port,
TRUE /*allow_mcast*/, &ipaddr, &port)) != OK)
return r;
err = udp_bind(udp->udp_pcb, &ipaddr, port);
return util_convert_err(err);
}
/*
* Connect a UDP socket to a remote address.
*/
static int
udpsock_connect(struct sock * sock, const struct sockaddr * addr,
socklen_t addr_len, endpoint_t user_endpt __unused)
{
struct udpsock *udp = (struct udpsock *)sock;
struct ifdev *ifdev;
const ip_addr_t *src_addr;
ip_addr_t dst_addr;
uint16_t dst_port;
uint32_t ifindex, ifindex2;
err_t err;
int r;
/*
* One may "unconnect" socket by providing an address with family
* AF_UNSPEC. Providing an <any>:0 address does not achieve the same.
*/
if (addr_is_unspec(addr, addr_len)) {
udp_disconnect(udp->udp_pcb);
return OK;
}
if ((r = ipsock_get_dst_addr(udpsock_get_ipsock(udp), addr,
addr_len, &udp->udp_pcb->local_ip, &dst_addr, &dst_port)) != OK)
return r;
/*
* Bind explicitly to a source address if the PCB is not bound to one
* yet. This is expected in the BSD socket API, but lwIP does not do
* it for us.
*/
if (ip_addr_isany(&udp->udp_pcb->local_ip)) {
/* Help the multicast case a bit, if possible. */
ifdev = NULL;
if (ip_addr_ismulticast(&dst_addr)) {
ifindex = pktsock_get_ifindex(&udp->udp_pktsock);
ifindex2 = udp_get_multicast_netif_index(udp->udp_pcb);
if (ifindex == 0)
ifindex = ifindex2;
if (ifindex != 0) {
ifdev = ifdev_get_by_index(ifindex);
if (ifdev == NULL)
return ENXIO;
}
}
src_addr = ifaddr_select(&dst_addr, ifdev, NULL /*ifdevp*/);
if (src_addr == NULL)
return EHOSTUNREACH;
err = udp_bind(udp->udp_pcb, src_addr,
udp->udp_pcb->local_port);
if (err != ERR_OK)
return util_convert_err(err);
}
/*
* Connecting a UDP socket serves two main purposes: 1) the socket uses
* the address as destination when sending, and 2) the socket receives
* packets from only the connected address.
*/
err = udp_connect(udp->udp_pcb, &dst_addr, dst_port);
if (err != ERR_OK)
return util_convert_err(err);
return OK;
}
/*
* Perform preliminary checks on a send request.
*/
static int
udpsock_pre_send(struct sock * sock, size_t len, socklen_t ctl_len __unused,
const struct sockaddr * addr, socklen_t addr_len __unused,
endpoint_t user_endpt __unused, int flags)
{
struct udpsock *udp = (struct udpsock *)sock;
if ((flags & ~MSG_DONTROUTE) != 0)
return EOPNOTSUPP;
if (!udpsock_is_conn(udp) && addr == NULL)
return EDESTADDRREQ;
/*
* This is only one part of the length check. The rest is done from
* udpsock_send(), once we have more information.
*/
if (len > ipsock_get_sndbuf(udpsock_get_ipsock(udp)))
return EMSGSIZE;
return OK;
}
/*
* Swap IP-level options between the UDP PCB and the packet options structure,
* for all options that have their flag set in the packet options structure.
* This function is called twice when sending a packet. The result is that the
* flagged options are overridden for only the packet being sent.
*/
static void
udpsock_swap_opt(struct udpsock * udp, struct pktopt * pkto)
{
uint8_t tos, ttl, mcast_ttl;
if (pkto->pkto_flags & PKTOF_TOS) {
tos = udp->udp_pcb->tos;
udp->udp_pcb->tos = pkto->pkto_tos;
pkto->pkto_tos = tos;
}
if (pkto->pkto_flags & PKTOF_TTL) {
ttl = udp->udp_pcb->ttl;
mcast_ttl = udp_get_multicast_ttl(udp->udp_pcb);
udp->udp_pcb->ttl = pkto->pkto_ttl;
udp_set_multicast_ttl(udp->udp_pcb, pkto->pkto_mcast_ttl);
pkto->pkto_ttl = ttl;
pkto->pkto_mcast_ttl = mcast_ttl;
}
}
/*
* Send a packet on a UDP socket.
*/
static int
udpsock_send(struct sock * sock, const struct sockdriver_data * data,
size_t len, size_t * off, const struct sockdriver_data * ctl,
socklen_t ctl_len, socklen_t * ctl_off __unused,
const struct sockaddr * addr, socklen_t addr_len,
endpoint_t user_endpt __unused, int flags, size_t min __unused)
{
struct udpsock *udp = (struct udpsock *)sock;
struct pktopt pktopt;
struct pbuf *pbuf;
struct ifdev *ifdev;
struct netif *netif;
const ip_addr_t *src_addrp, *dst_addrp;
ip_addr_t src_addr, dst_addr; /* for storage only; not always used! */
uint16_t dst_port;
uint32_t ifindex;
size_t hdrlen;
err_t err;
int r;
/* Copy in and parse any packet options. */
pktopt.pkto_flags = 0;
if ((r = pktsock_get_ctl(&udp->udp_pktsock, ctl, ctl_len,
&pktopt)) != OK)
return r;
/*
* The code below will both determine an outgoing interface and a
* source address for the packet. Even though lwIP could do this for
* us in some cases, there are other cases where we must do so
* ourselves, with as main reasons 1) the possibility that either or
* both have been provided through IPV6_PKTINFO, and 2) our intent to
* detect and stop zone violations for (combinations of) scoped IPv6
* addresses. As a result, it is easier to simply take over the
* selection tasks lwIP in their entirety.
*
* Much of the same applies to rawsock_send() as well. Functional
* differences (e.g. IP_HDRINCL support) as well as the PCB accesses in
* the code make it hard to merge the two into a single pktsock copy.
* Please do keep the two in sync as much as possible.
*/
/*
* Start by checking whether the source address and/or the outgoing
* interface are overridden using sticky and/or ancillary options. The
* call to pktsock_get_pktinfo(), if successful, will either set
* 'ifdev' to NULL, in which case there is no override, or it will set
* 'ifdev' to the outgoing interface to use, and (only) in that case
* also fill 'src_addr', with an address that may either be a locally
* owned unicast address or the unspecified ('any') address. If it is
* a unicast address, that is the source address to use for the packet.
* Otherwise, fall back to the address to which the socket is bound,
* which may also be the unspecified address or even a multicast
* address. In those case we will pick a source address further below.
*/
if ((r = pktsock_get_pktinfo(&udp->udp_pktsock, &pktopt, &ifdev,
&src_addr)) != OK)
return r;
if (ifdev != NULL && !ip_addr_isany(&src_addr)) {
/* This is guaranteed to be a proper local unicast address. */
src_addrp = &src_addr;
} else {
src_addrp = &udp->udp_pcb->local_ip;
/*
* If the socket is bound to a multicast address, use the
* unspecified ('any') address as source address instead, until
* we select a real source address (further below). This
* substitution keeps the rest of the code a bit simpler.
*/
if (ip_addr_ismulticast(src_addrp))
src_addrp = IP46_ADDR_ANY(IP_GET_TYPE(src_addrp));
}
/*
* Determine the destination address to use. If the socket is
* connected, always ignore any address provided in the send call.
*/
if (!udpsock_is_conn(udp)) {
assert(addr != NULL); /* already checked in pre_send */
if ((r = ipsock_get_dst_addr(udpsock_get_ipsock(udp), addr,
addr_len, src_addrp, &dst_addr, &dst_port)) != OK)
return r;
dst_addrp = &dst_addr;
} else {
dst_addrp = &udp->udp_pcb->remote_ip;
dst_port = udp->udp_pcb->remote_port;
}
/*
* If the destination is a multicast address, select the outgoing
* interface based on the multicast interface index, if one is set.
* This must be done here in order to allow the code further below to
* detect zone violations, because if we leave this selection to lwIP,
* it will not perform zone violation detection at all. Also note that
* this case must *not* override an interface index already specified
* using IPV6_PKTINFO, as per RFC 3542 Sec. 6.7.
*/
if (ifdev == NULL && ip_addr_ismulticast(dst_addrp)) {
ifindex = udp_get_multicast_netif_index(udp->udp_pcb);
if (ifindex != NETIF_NO_INDEX)
ifdev = ifdev_get_by_index(ifindex); /* (may fail) */
}
/*
* If an interface has been determined already now, the send operation
* will bypass routing. In that case, we must perform our own checks
* on address zone violations, because those will not be made anywhere
* else. Subsequent steps below will never introduce violations.
*/
if (ifdev != NULL && IP_IS_V6(dst_addrp)) {
if (ifaddr_is_zone_mismatch(ip_2_ip6(dst_addrp), ifdev))
return EHOSTUNREACH;
if (IP_IS_V6(src_addrp) &&
ifaddr_is_zone_mismatch(ip_2_ip6(src_addrp), ifdev))
return EHOSTUNREACH;
}
/*
* If we do not yet have an interface at this point, perform a route
* lookup to determine the outgoing interface. Unless MSG_DONTROUTE is
* set (which covers SO_DONTROUTE as well), in which case we look for a
* local subnet that matches the destination address.
*/
if (ifdev == NULL) {
if (!(flags & MSG_DONTROUTE)) {
/*
* ip_route() should never be called with an
* IPADDR_TYPE_ANY type address. This is a lwIP-
* internal requirement; while we override both routing
* functions, we do not deviate from it.
*/
if (IP_IS_ANY_TYPE_VAL(*src_addrp))
src_addrp =
IP46_ADDR_ANY(IP_GET_TYPE(dst_addrp));
/* Perform the route lookup. */
if ((netif = ip_route(src_addrp, dst_addrp)) == NULL)
return EHOSTUNREACH;
ifdev = netif_get_ifdev(netif);
} else {
if ((ifdev = ifaddr_map_by_subnet(dst_addrp)) == NULL)
return EHOSTUNREACH;
}
}
/*
* At this point we have an outgoing interface. If we do not have a
* source address yet, pick one now.
*/
assert(ifdev != NULL);
if (ip_addr_isany(src_addrp)) {
src_addrp = ifaddr_select(dst_addrp, ifdev, NULL /*ifdevp*/);
if (src_addrp == NULL)
return EHOSTUNREACH;
}
/*
* Now that we know the full conditions of what we are about to send,
* check whether the packet size leaves enough room for lwIP to prepend
* headers. If so, allocate a chain of pbufs for the packet.
*/
assert(len <= UDP_MAX_PAYLOAD);
if (IP_IS_V6(dst_addrp))
hdrlen = IP6_HLEN + UDP_HLEN;
else
hdrlen = IP_HLEN + UDP_HLEN;
if (hdrlen + len > UDP_MAX_PAYLOAD)
return EMSGSIZE;
if ((pbuf = pchain_alloc(PBUF_TRANSPORT, len)) == NULL)
return ENOBUFS;
/* Copy in the packet data. */
if ((r = pktsock_get_data(&udp->udp_pktsock, data, len, pbuf)) != OK) {
pbuf_free(pbuf);
return r;
}
/*
* Set broadcast/multicast flags for accounting purposes. Only the
* multicast flag is used for output accounting, but for loopback
* traffic, both flags are copied and used for input accounting and
* setting MSG_MCAST/MSG_BCAST.
*/
if (ip_addr_ismulticast(dst_addrp))
pbuf->flags |= PBUF_FLAG_LLMCAST;
else if (ip_addr_isbroadcast(dst_addrp, ifdev_get_netif(ifdev)))
pbuf->flags |= PBUF_FLAG_LLBCAST;
/* Send the packet. */
udpsock_swap_opt(udp, &pktopt);
assert(!ip_addr_isany(src_addrp));
assert(!ip_addr_ismulticast(src_addrp));
err = udp_sendto_if_src(udp->udp_pcb, pbuf, dst_addrp, dst_port,
ifdev_get_netif(ifdev), src_addrp);
udpsock_swap_opt(udp, &pktopt);
/* Free the pbuf, as a copy has been made. */
pbuf_free(pbuf);
/*
* On success, make sure to return the size of the sent packet as well.
* As an aside: ctl_off need not be updated, as it is not returned.
*/
if ((r = util_convert_err(err)) == OK)
*off = len;
return r;
}
/*
* Update the set of flag-type socket options on a UDP socket.
*/
static void
udpsock_setsockmask(struct sock * sock, unsigned int mask)
{
struct udpsock *udp = (struct udpsock *)sock;
if (mask & SO_REUSEADDR)
ip_set_option(udp->udp_pcb, SOF_REUSEADDR);
else
ip_reset_option(udp->udp_pcb, SOF_REUSEADDR);
if (mask & SO_BROADCAST)
ip_set_option(udp->udp_pcb, SOF_BROADCAST);
else
ip_reset_option(udp->udp_pcb, SOF_BROADCAST);
}
/*
* Prepare a helper structure for IP-level option processing.
*/
static void
udpsock_get_ipopts(struct udpsock * udp, struct ipopts * ipopts)
{
ipopts->local_ip = &udp->udp_pcb->local_ip;
ipopts->remote_ip = &udp->udp_pcb->remote_ip;
ipopts->tos = &udp->udp_pcb->tos;
ipopts->ttl = &udp->udp_pcb->ttl;
ipopts->sndmin = UDP_SNDBUF_MIN;
ipopts->sndmax = UDP_SNDBUF_MAX;
ipopts->rcvmin = UDP_RCVBUF_MIN;
ipopts->rcvmax = UDP_RCVBUF_MAX;
}
/*
* Set socket options on a UDP socket.
*/
static int
udpsock_setsockopt(struct sock * sock, int level, int name,
const struct sockdriver_data * data, socklen_t len)
{
struct udpsock *udp = (struct udpsock *)sock;
struct ipopts ipopts;
ip_addr_t ipaddr;
struct in_addr in_addr;
struct ifdev *ifdev;
unsigned int flags;
uint32_t ifindex;
uint8_t byte;
int r, val;
/*
* Unfortunately, we have to duplicate most of the multicast options
* rather than sharing them with rawsock at the pktsock level. The
* reason is that each of the PCBs have their own multicast abstraction
* functions and so we cannot merge the rest. Same for getsockopt.
*/
switch (level) {
case IPPROTO_IP:
if (udpsock_is_ipv6(udp))
break;
switch (name) {
case IP_MULTICAST_IF:
pktsock_set_mcaware(&udp->udp_pktsock);
if ((r = sockdriver_copyin_opt(data, &in_addr,
sizeof(in_addr), len)) != OK)
return r;
ip_addr_set_ip4_u32(&ipaddr, in_addr.s_addr);
if ((ifdev = ifaddr_map_by_addr(&ipaddr)) == NULL)
return EADDRNOTAVAIL;
udp_set_multicast_netif_index(udp->udp_pcb,
ifdev_get_index(ifdev));
return OK;
case IP_MULTICAST_LOOP:
pktsock_set_mcaware(&udp->udp_pktsock);
if ((r = sockdriver_copyin_opt(data, &byte,
sizeof(byte), len)) != OK)
return r;
flags = udp_flags(udp->udp_pcb);
if (byte)
flags |= UDP_FLAGS_MULTICAST_LOOP;
else
flags &= ~UDP_FLAGS_MULTICAST_LOOP;
udp_setflags(udp->udp_pcb, flags);
return OK;
case IP_MULTICAST_TTL:
pktsock_set_mcaware(&udp->udp_pktsock);
if ((r = sockdriver_copyin_opt(data, &byte,
sizeof(byte), len)) != OK)
return r;
udp_set_multicast_ttl(udp->udp_pcb, byte);
return OK;
}
break;
case IPPROTO_IPV6:
if (!udpsock_is_ipv6(udp))
break;
switch (name) {
case IPV6_MULTICAST_IF:
pktsock_set_mcaware(&udp->udp_pktsock);
if ((r = sockdriver_copyin_opt(data, &val, sizeof(val),
len)) != OK)
return r;
if (val != 0) {
ifindex = (uint32_t)val;
ifdev = ifdev_get_by_index(ifindex);
if (ifdev == NULL)
return ENXIO;
} else
ifindex = NETIF_NO_INDEX;
udp_set_multicast_netif_index(udp->udp_pcb, ifindex);
return OK;
case IPV6_MULTICAST_LOOP:
pktsock_set_mcaware(&udp->udp_pktsock);
if ((r = sockdriver_copyin_opt(data, &val, sizeof(val),
len)) != OK)
return r;
if (val < 0 || val > 1)
return EINVAL;
flags = udp_flags(udp->udp_pcb);
if (val)
flags |= UDP_FLAGS_MULTICAST_LOOP;
else
flags &= ~UDP_FLAGS_MULTICAST_LOOP;
/*
* lwIP's IPv6 functionality does not actually check
* this flag at all yet. We set it in the hope that
* one day this will magically start working.
*/
udp_setflags(udp->udp_pcb, flags);
return OK;
case IPV6_MULTICAST_HOPS:
pktsock_set_mcaware(&udp->udp_pktsock);
if ((r = sockdriver_copyin_opt(data, &val, sizeof(val),
len)) != OK)
return r;
if (val < -1 || val > UINT8_MAX)
return EINVAL;
if (val == -1)
val = 1;
udp_set_multicast_ttl(udp->udp_pcb, val);
return OK;
}
break;
}
/* Handle all other options at the packet or IP level. */
udpsock_get_ipopts(udp, &ipopts);
return pktsock_setsockopt(&udp->udp_pktsock, level, name, data, len,
&ipopts);
}
/*
* Retrieve socket options on a UDP socket.
*/
static int
udpsock_getsockopt(struct sock * sock, int level, int name,
const struct sockdriver_data * data, socklen_t * len)
{
struct udpsock *udp = (struct udpsock *)sock;
struct ipopts ipopts;
const ip4_addr_t *ip4addr;
struct in_addr in_addr;
struct ifdev *ifdev;
unsigned int flags;
uint32_t ifindex;
uint8_t byte;
int val;
switch (level) {
case IPPROTO_IP:
if (udpsock_is_ipv6(udp))
break;
switch (name) {
case IP_MULTICAST_IF:
ifindex = udp_get_multicast_netif_index(udp->udp_pcb);
/*
* Map back from the interface index to the IPv4
* address assigned to the corresponding interface.
* Should this not work out, return the 'any' address.
*/
if (ifindex != NETIF_NO_INDEX &&
(ifdev = ifdev_get_by_index(ifindex)) != NULL) {
ip4addr =
netif_ip4_addr(ifdev_get_netif(ifdev));
in_addr.s_addr = ip4_addr_get_u32(ip4addr);
} else
in_addr.s_addr = PP_HTONL(INADDR_ANY);
return sockdriver_copyout_opt(data, &in_addr,
sizeof(in_addr), len);
case IP_MULTICAST_LOOP:
flags = udp_flags(udp->udp_pcb);
byte = !!(flags & UDP_FLAGS_MULTICAST_LOOP);
return sockdriver_copyout_opt(data, &byte,
sizeof(byte), len);
case IP_MULTICAST_TTL:
byte = udp_get_multicast_ttl(udp->udp_pcb);
return sockdriver_copyout_opt(data, &byte,
sizeof(byte), len);
}
break;
case IPPROTO_IPV6:
if (!udpsock_is_ipv6(udp))
break;
switch (name) {
case IPV6_MULTICAST_IF:
ifindex = udp_get_multicast_netif_index(udp->udp_pcb);
val = (int)ifindex;
return sockdriver_copyout_opt(data, &val, sizeof(val),
len);
case IPV6_MULTICAST_LOOP:
flags = udp_flags(udp->udp_pcb);
val = !!(flags & UDP_FLAGS_MULTICAST_LOOP);
return sockdriver_copyout_opt(data, &val, sizeof(val),
len);
case IPV6_MULTICAST_HOPS:
val = udp_get_multicast_ttl(udp->udp_pcb);
return sockdriver_copyout_opt(data, &val, sizeof(val),
len);
}
break;
}
/* Handle all other options at the packet or IP level. */
udpsock_get_ipopts(udp, &ipopts);
return pktsock_getsockopt(&udp->udp_pktsock, level, name, data, len,
&ipopts);
}
/*
* Retrieve the local socket address of a UDP socket.
*/
static int
udpsock_getsockname(struct sock * sock, struct sockaddr * addr,
socklen_t * addr_len)
{
struct udpsock *udp = (struct udpsock *)sock;
ipsock_put_addr(udpsock_get_ipsock(udp), addr, addr_len,
&udp->udp_pcb->local_ip, udp->udp_pcb->local_port);
return OK;
}
/*
* Retrieve the remote socket address of a UDP socket.
*/
static int
udpsock_getpeername(struct sock * sock, struct sockaddr * addr,
socklen_t * addr_len)
{
struct udpsock *udp = (struct udpsock *)sock;
if (!udpsock_is_conn(udp))
return ENOTCONN;
ipsock_put_addr(udpsock_get_ipsock(udp), addr, addr_len,
&udp->udp_pcb->remote_ip, udp->udp_pcb->remote_port);
return OK;
}
/*
* Shut down a UDP socket for reading and/or writing.
*/
static int
udpsock_shutdown(struct sock * sock, unsigned int mask)
{
struct udpsock *udp = (struct udpsock *)sock;
if (mask & SFL_SHUT_RD)
udp_recv(udp->udp_pcb, NULL, NULL);
pktsock_shutdown(&udp->udp_pktsock, mask);
return OK;
}
/*
* Close a UDP socket.
*/
static int
udpsock_close(struct sock * sock, int force __unused)
{
struct udpsock *udp = (struct udpsock *)sock;
udp_recv(udp->udp_pcb, NULL, NULL);
udp_remove(udp->udp_pcb);
udp->udp_pcb = NULL;
pktsock_close(&udp->udp_pktsock);
return OK;
}
/*
* Free up a closed UDP socket.
*/
static void
udpsock_free(struct sock * sock)
{
struct udpsock *udp = (struct udpsock *)sock;
assert(udp->udp_pcb == NULL);
SIMPLEQ_INSERT_HEAD(&udp_freelist, udp, udp_next);
}
/*
* Fill the given kinfo_pcb sysctl(7) structure with information about the UDP
* PCB identified by the given pointer.
*/
static void
udpsock_get_info(struct kinfo_pcb * ki, const void * ptr)
{
const struct udp_pcb *pcb = (const struct udp_pcb *)ptr;
struct udpsock *udp;
ki->ki_type = SOCK_DGRAM;
/*
* All UDP sockets should be created by this module, but protect
* ourselves from the case that that is not true anyway.
*/
if (pcb->recv_arg != NULL) {
udp = (struct udpsock *)pcb->recv_arg;
assert(udp >= udp_array &&
udp < &udp_array[__arraycount(udp_array)]);
} else
udp = NULL;
ipsock_get_info(ki, &pcb->local_ip, pcb->local_port, &pcb->remote_ip,
pcb->remote_port);
if (udp != NULL) {
/* TODO: change this so that sockstat(1) may work one day. */
ki->ki_sockaddr = (uint64_t)(uintptr_t)udpsock_get_sock(udp);
ki->ki_rcvq = pktsock_get_recvlen(&udp->udp_pktsock);
}
}
/*
* Given either NULL or a previously returned UDP PCB pointer, return the first
* or next UDP PCB pointer, or NULL if there are no more. Skip UDP PCBs that
* are not bound to an address, as there is no use reporting them.
*/
static const void *
udpsock_enum(const void * last)
{
const struct udp_pcb *pcb;
if (last != NULL)
pcb = (const void *)((const struct udp_pcb *)last)->next;
else
pcb = (const void *)udp_pcbs;
while (pcb != NULL && pcb->local_port == 0)
pcb = pcb->next;
return pcb;
}
/*
* Obtain the list of UDP protocol control blocks, for sysctl(7).
*/
static ssize_t
udpsock_pcblist(struct rmib_call * call, struct rmib_node * node __unused,
struct rmib_oldp * oldp, struct rmib_newp * newp __unused)
{
return util_pcblist(call, oldp, udpsock_enum, udpsock_get_info);
}
static const struct sockevent_ops udpsock_ops = {
.sop_bind = udpsock_bind,
.sop_connect = udpsock_connect,
.sop_pre_send = udpsock_pre_send,
.sop_send = udpsock_send,
.sop_pre_recv = pktsock_pre_recv,
.sop_recv = pktsock_recv,
.sop_test_recv = pktsock_test_recv,
.sop_ioctl = ifconf_ioctl,
.sop_setsockmask = udpsock_setsockmask,
.sop_setsockopt = udpsock_setsockopt,
.sop_getsockopt = udpsock_getsockopt,
.sop_getsockname = udpsock_getsockname,
.sop_getpeername = udpsock_getpeername,
.sop_shutdown = udpsock_shutdown,
.sop_close = udpsock_close,
.sop_free = udpsock_free
};