/*
* udp.c
*
* Written by Alexander Motin <mav@FreeBSD.org>
*/
#include "ppp.h"
#include "phys.h"
#include "mbuf.h"
#include "udp.h"
#include "ngfunc.h"
#include "util.h"
#include "log.h"
#include <netgraph/ng_message.h>
#ifdef __DragonFly__
#include <netgraph/socket/ng_socket.h>
#include <netgraph/ksocket/ng_ksocket.h>
#else
#include <netgraph/ng_socket.h>
#include <netgraph/ng_ksocket.h>
#endif
#include <netgraph.h>
/*
* XXX this device type not completely correct,
* as it can deliver out-of-order frames. This can make problems
* for different compression and encryption protocols.
*/
/*
* DEFINITIONS
*/
#define UDP_MTU 2048
#define UDP_MRU 2048
#define UDP_REOPEN_PAUSE 5
#define UDP_MAXPARENTIFS 256
struct udpinfo {
struct {
struct optinfo options;
struct u_addr self_addr; /* Configured local IP address */
struct u_range peer_addr; /* Configured peer IP address */
in_port_t self_port; /* Configured local port */
in_port_t peer_port; /* Configured peer port */
} conf;
/* State */
u_char incoming:1; /* incoming vs. outgoing */
struct UdpIf *If;
struct u_addr peer_addr;
in_port_t peer_port;
ng_ID_t node_id;
};
typedef struct udpinfo *UdpInfo;
/* Set menu options */
enum {
SET_PEERADDR,
SET_SELFADDR,
SET_ENABLE,
SET_DISABLE,
};
enum {
UDP_CONF_ORIGINATE, /* allow originating connections to peer */
UDP_CONF_INCOMING, /* allow accepting connections from peer */
};
/*
* INTERNAL FUNCTIONS
*/
static int UdpInit(PhysInfo p);
static void UdpOpen(PhysInfo p);
static void UdpClose(PhysInfo p);
static void UdpStat(Context ctx);
static int UdpOrigination(PhysInfo p);
static int UdpIsSync(PhysInfo p);
static int UdpPeerAddr(PhysInfo p, void *buf, int buf_len);
static int UdpPeerPort(PhysInfo p, void *buf, int buf_len);
static int UdpCallingNum(PhysInfo p, void *buf, int buf_len);
static int UdpCalledNum(PhysInfo p, void *buf, int buf_len);
static void UdpDoClose(PhysInfo p);
static int UdpSetCommand(Context ctx, int ac, char *av[], void *arg);
/*
* GLOBAL VARIABLES
*/
const struct phystype gUdpPhysType = {
.name = "udp",
.minReopenDelay = UDP_REOPEN_PAUSE,
.mtu = UDP_MTU,
.mru = UDP_MRU,
.init = UdpInit,
.open = UdpOpen,
.close = UdpClose,
.showstat = UdpStat,
.originate = UdpOrigination,
.issync = UdpIsSync,
.peeraddr = UdpPeerAddr,
.peerport = UdpPeerPort,
.callingnum = UdpCallingNum,
.callednum = UdpCalledNum,
};
const struct cmdtab UdpSetCmds[] = {
{ "self ip [port]", "Set local IP address",
UdpSetCommand, NULL, (void *) SET_SELFADDR },
{ "peer ip [port]", "Set remote IP address",
UdpSetCommand, NULL, (void *) SET_PEERADDR },
{ "enable [opt ...]", "Enable option",
UdpSetCommand, NULL, (void *) SET_ENABLE },
{ "disable [opt ...]", "Disable option",
UdpSetCommand, NULL, (void *) SET_DISABLE },
{ NULL },
};
static struct confinfo gConfList[] = {
{ 0, UDP_CONF_ORIGINATE, "originate" },
{ 0, UDP_CONF_INCOMING, "incoming" },
{ 0, 0, NULL },
};
struct UdpIf {
struct u_addr self_addr;
in_port_t self_port;
int csock; /* netgraph Control socket */
EventRef ctrlEvent; /* listen for ctrl messages */
};
int UdpIfCount=0;
struct UdpIf UdpIfs[UDP_MAXPARENTIFS];
int UdpListenUpdateSheduled=0;
struct pppTimer UdpListenUpdateTimer;
/*
* UdpInit()
*/
static int
UdpInit(PhysInfo p)
{
UdpInfo pi;
pi = (UdpInfo) (p->info = Malloc(MB_PHYS, sizeof(*pi)));
u_addrclear(&pi->conf.self_addr);
u_rangeclear(&pi->conf.peer_addr);
pi->conf.self_port=0;
pi->conf.peer_port=0;
pi->incoming = 0;
pi->If = NULL;
u_addrclear(&pi->peer_addr);
pi->peer_port=0;
return(0);
}
/*
* UdpOpen()
*/
static void
UdpOpen(PhysInfo p)
{
UdpInfo const pi = (UdpInfo) p->info;
char path[NG_PATHLEN+1];
char hook[NG_HOOKLEN+1];
struct ngm_mkpeer mkp;
struct ngm_name nm;
struct sockaddr_storage addr;
union {
u_char buf[sizeof(struct ng_ksocket_sockopt) + sizeof(int)];
struct ng_ksocket_sockopt ksso;
} u;
struct ng_ksocket_sockopt *const ksso = &u.ksso;
union {
u_char buf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
struct ng_mesg reply;
} repbuf;
struct ng_mesg *const reply = &repbuf.reply;
struct nodeinfo *ninfo = (struct nodeinfo *)&reply->data;
int csock;
/* Create a new netgraph node to control TCP ksocket node. */
if (NgMkSockNode(NULL, &csock, NULL) < 0) {
Log(LG_ERR, ("[%s] TCP can't create control socket: %s",
p->name, strerror(errno)));
goto fail;
}
(void)fcntl(csock, F_SETFD, 1);
if (!PhysGetUpperHook(p, path, hook)) {
Log(LG_PHYS, ("[%s] UDP: can't get upper hook", p->name));
goto fail;
}
/* Attach ksocket node to PPP node */
snprintf(mkp.type, sizeof(mkp.type), "%s", NG_KSOCKET_NODE_TYPE);
snprintf(mkp.ourhook, sizeof(mkp.ourhook), hook);
if ((pi->conf.self_addr.family==AF_INET6) ||
(pi->conf.self_addr.family==AF_UNSPEC && pi->conf.peer_addr.addr.family==AF_INET6)) {
snprintf(mkp.peerhook, sizeof(mkp.peerhook), "%d/%d/%d", PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
} else {
snprintf(mkp.peerhook, sizeof(mkp.peerhook), "inet/dgram/udp");
}
if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
Log(LG_ERR, ("[%s] can't attach %s node: %s",
p->name, NG_KSOCKET_NODE_TYPE, strerror(errno)));
goto fail;
}
strlcat(path, ".", sizeof(path));
strlcat(path, hook, sizeof(path));
/* Give it a name */
snprintf(nm.name, sizeof(nm.name), "mpd%d-%s", gPid, p->name);
if (NgSendMsg(csock, path,
NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
Log(LG_ERR, ("[%s] can't name %s node: %s",
p->name, NG_BPF_NODE_TYPE, strerror(errno)));
}
/* Get ksocket node ID */
if (NgSendMsg(csock, path,
NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) != -1) {
if (NgRecvMsg(csock, reply, sizeof(repbuf), NULL) != -1) {
pi->node_id = ninfo->id;
}
}
if ((pi->incoming) || (pi->conf.self_port != 0)) {
/* Setsockopt socket. */
ksso->level=SOL_SOCKET;
ksso->name=SO_REUSEPORT;
((int *)(ksso->value))[0]=1;
if (NgSendMsg(csock, path, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_SETOPT, &u, sizeof(u)) < 0) {
Log(LG_ERR, ("[%s] can't setsockopt() %s node: %s",
p->name, NG_KSOCKET_NODE_TYPE, strerror(errno)));
goto fail;
}
/* Bind socket */
u_addrtosockaddr(&pi->conf.self_addr, pi->conf.self_port, &addr);
if (NgSendMsg(csock, path, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_BIND, &addr, addr.ss_len) < 0) {
Log(LG_ERR, ("[%s] can't bind() %s node: %s",
p->name, NG_KSOCKET_NODE_TYPE, strerror(errno)));
goto fail;
}
}
if (!pi->incoming) {
if ((!u_rangeempty(&pi->conf.peer_addr)) && (pi->conf.peer_port != 0)) {
u_addrcopy(&pi->conf.peer_addr.addr,&pi->peer_addr);
pi->peer_port = pi->conf.peer_port;
} else {
Log(LG_ERR, ("[%s] Can't connect without peer specified", p->name));
goto fail;
}
}
u_addrtosockaddr(&pi->peer_addr, pi->peer_port, &addr);
/* Connect socket if peer address and port is specified */
if (NgSendMsg(csock, path, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_CONNECT, &addr, addr.ss_len) < 0) {
Log(LG_ERR, ("[%s] can't connect() %s node: %s",
p->name, NG_KSOCKET_NODE_TYPE, strerror(errno)));
goto fail;
}
close(csock);
/* OK */
p->state = PHYS_STATE_UP;
PhysUp(p);
return;
fail:
UdpDoClose(p);
pi->incoming=0;
p->state = PHYS_STATE_DOWN;
u_addrclear(&pi->peer_addr);
pi->peer_port=0;
PhysDown(p, STR_ERROR, NULL);
if (csock>0)
close(csock);
}
/*
* UdpClose()
*/
static void
UdpClose(PhysInfo p)
{
UdpInfo const pi = (UdpInfo) p->info;
if (p->state != PHYS_STATE_DOWN) {
UdpDoClose(p);
pi->incoming=0;
p->state = PHYS_STATE_DOWN;
u_addrclear(&pi->peer_addr);
pi->peer_port=0;
PhysDown(p, 0, NULL);
}
}
/*
* UdpDoClose()
*/
static void
UdpDoClose(PhysInfo p)
{
UdpInfo const pi = (UdpInfo) p->info;
char path[NG_PATHLEN + 1];
int csock;
if (pi->node_id == 0)
return;
/* Get a temporary netgraph socket node */
if (NgMkSockNode(NULL, &csock, NULL) == -1) {
Log(LG_ERR, ("UDP: NgMkSockNode: %s", strerror(errno)));
return;
}
/* Disconnect session hook. */
snprintf(path, sizeof(path), "[%lx]:", (u_long)pi->node_id);
NgFuncShutdownNode(csock, p->name, path);
close(csock);
pi->node_id = 0;
}
/*
* UdpOrigination()
*/
static int
UdpOrigination(PhysInfo p)
{
UdpInfo const pi = (UdpInfo) p->info;
return (pi->incoming ? LINK_ORIGINATE_REMOTE : LINK_ORIGINATE_LOCAL);
}
/*
* UdpIsSync()
*/
static int
UdpIsSync(PhysInfo p)
{
return (1);
}
static int
UdpPeerAddr(PhysInfo p, void *buf, int buf_len)
{
UdpInfo const pi = (UdpInfo) p->info;
if (u_addrtoa(&pi->peer_addr, buf, buf_len))
return(0);
else
return(-1);
}
static int
UdpPeerPort(PhysInfo p, void *buf, int buf_len)
{
UdpInfo const pi = (UdpInfo) p->info;
if (snprintf(buf, buf_len, "%d", pi->peer_port))
return(0);
else
return(-1);
}
static int
UdpCallingNum(PhysInfo p, void *buf, int buf_len)
{
UdpInfo const pi = (UdpInfo) p->info;
if (pi->incoming) {
if (u_addrtoa(&pi->peer_addr, buf, buf_len))
return (0);
else
return (-1);
} else {
if (u_addrtoa(&pi->conf.self_addr, buf, buf_len))
return (0);
else
return (-1);
}
}
static int
UdpCalledNum(PhysInfo p, void *buf, int buf_len)
{
UdpInfo const pi = (UdpInfo) p->info;
if (!pi->incoming) {
if (u_addrtoa(&pi->peer_addr, buf, buf_len))
return (0);
else
return (-1);
} else {
if (u_addrtoa(&pi->conf.self_addr, buf, buf_len))
return (0);
else
return (-1);
}
}
/*
* UdpStat()
*/
void
UdpStat(Context ctx)
{
UdpInfo const pi = (UdpInfo) ctx->phys->info;
char buf[64];
Printf("UDP configuration:\r\n");
Printf("\tSelf address : %s, port %u\r\n",
u_addrtoa(&pi->conf.self_addr, buf, sizeof(buf)), pi->conf.self_port);
Printf("\tPeer address : %s, port %u\r\n",
u_rangetoa(&pi->conf.peer_addr, buf, sizeof(buf)), pi->conf.peer_port);
Printf("UDP options:\r\n");
OptStat(ctx, &pi->conf.options, gConfList);
Printf("UDP state:\r\n");
Printf("\tState : %s\r\n", gPhysStateNames[ctx->phys->state]);
if (ctx->phys->state != PHYS_STATE_DOWN) {
Printf("\tIncoming : %s\r\n", (pi->incoming?"YES":"NO"));
Printf("\tCurrent peer : %s, port %u\r\n",
u_addrtoa(&pi->peer_addr, buf, sizeof(buf)), pi->peer_port);
}
}
/*
* UdpAcceptEvent() triggers when we accept incoming connection.
*/
static void
UdpAcceptEvent(int type, void *cookie)
{
struct sockaddr_storage saddr;
socklen_t saddrlen;
struct u_addr addr;
in_port_t port;
char buf[64];
char buf1[64];
int k;
struct UdpIf *If=(struct UdpIf *)(cookie);
time_t const now = time(NULL);
PhysInfo p = NULL;
UdpInfo pi = NULL;
char pktbuf[UDP_MRU+100];
char pktlen;
assert(type == EVENT_READ);
saddrlen = sizeof(saddr);
if ((pktlen = recvfrom(If->csock, pktbuf, sizeof(pktbuf), MSG_DONTWAIT, (struct sockaddr *)(&saddr), &saddrlen)) < 0) {
Log(LG_PHYS, ("recvfrom() error: %s", strerror(errno)));
}
sockaddrtou_addr(&saddr, &addr, &port);
Log(LG_PHYS, ("Incoming UDP connection from %s %u to %s %u",
u_addrtoa(&addr, buf, sizeof(buf)), port,
u_addrtoa(&If->self_addr, buf1, sizeof(buf1)), If->self_port));
if (gShutdownInProgress) {
Log(LG_PHYS, ("Shutdown sequence in progress, ignoring request."));
goto failed;
}
if (OVERLOAD()) {
Log(LG_PHYS, ("Daemon overloaded, ignoring request."));
goto failed;
}
/* Examine all UDP links. */
for (k = 0; k < gNumPhyses; k++) {
PhysInfo p2;
UdpInfo pi2;
if (gPhyses[k] && gPhyses[k]->type != &gUdpPhysType)
continue;
p2 = gPhyses[k];
pi2 = (UdpInfo)p2->info;
if ((p2->state == PHYS_STATE_DOWN) &&
(now - p2->lastClose >= UDP_REOPEN_PAUSE) &&
Enabled(&pi2->conf.options, UDP_CONF_INCOMING) &&
(pi2->If == If) &&
IpAddrInRange(&pi2->conf.peer_addr, &addr) &&
(pi2->conf.peer_port == 0 || pi2->conf.peer_port == port)) {
if (pi == NULL || pi2->conf.peer_addr.width > pi->conf.peer_addr.width) {
p = p2;
pi = pi2;
if (u_rangehost(&pi->conf.peer_addr)) {
break; /* Nothing could be better */
}
}
}
}
if (pi != NULL) {
Log(LG_PHYS, ("[%s] Accepting UDP connection from %s %u to %s %u",
p->name, u_addrtoa(&addr, buf, sizeof(buf)), port,
u_addrtoa(&If->self_addr, buf1, sizeof(buf1)), If->self_port));
sockaddrtou_addr(&saddr, &pi->peer_addr, &pi->peer_port);
pi->incoming=1;
p->state = PHYS_STATE_READY;
PhysIncoming(p);
} else {
Log(LG_PHYS, ("No free UDP link with requested parameters "
"was found"));
}
failed:
EventRegister(&If->ctrlEvent, EVENT_READ, If->csock,
0, UdpAcceptEvent, If);
}
static int
ListenUdpNode(struct UdpIf *If)
{
struct sockaddr_storage addr;
int error;
char buf[64];
int opt;
/* Make listening UDP socket. */
if (If->self_addr.family==AF_INET6) {
If->csock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
} else {
If->csock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
}
(void)fcntl(If->csock, F_SETFD, 1);
/* Setsockopt socket. */
opt = 1;
if (setsockopt(If->csock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))) {
Log(LG_ERR, ("UDP: can't setsockopt socket: %s",
strerror(errno)));
error = errno;
goto fail2;
};
/* Bind socket. */
u_addrtosockaddr(&If->self_addr, If->self_port, &addr);
if (bind(If->csock, (struct sockaddr *)(&addr), addr.ss_len)) {
Log(LG_ERR, ("UDP: can't bind socket: %s",
strerror(errno)));
error = errno;
goto fail2;
}
Log(LG_PHYS, ("UDP: waiting for connection on %s %u",
u_addrtoa(&If->self_addr, buf, sizeof(buf)), If->self_port));
EventRegister(&If->ctrlEvent, EVENT_READ, If->csock,
0, UdpAcceptEvent, If);
return (1);
fail2:
close(If->csock);
If->csock = -1;
return (0);
};
/*
* UdpListenUpdate()
*/
static void
UdpListenUpdate(void *arg)
{
int k;
UdpListenUpdateSheduled = 0;
/* Examine all UDP links. */
for (k = 0; k < gNumPhyses; k++) {
PhysInfo p;
UdpInfo pi;
int i, j = -1;
if (gPhyses[k] == NULL ||
gPhyses[k]->type != &gUdpPhysType)
continue;
p = gPhyses[k];
pi = (UdpInfo)p->info;
if (!Enabled(&pi->conf.options, UDP_CONF_INCOMING))
continue;
if (!pi->conf.self_port) {
Log(LG_ERR, ("UDP: Skipping link %s with undefined "
"port number", p->name));
continue;
}
for (i = 0; i < UdpIfCount; i++)
if ((u_addrcompare(&UdpIfs[i].self_addr, &pi->conf.self_addr) == 0) &&
(UdpIfs[i].self_port == pi->conf.self_port))
j = i;
if (j == -1) {
if (UdpIfCount>=UDP_MAXPARENTIFS) {
Log(LG_ERR, ("[%s] UDP: Too many different listening ports! ",
p->name));
continue;
}
u_addrcopy(&pi->conf.self_addr,&UdpIfs[UdpIfCount].self_addr);
UdpIfs[UdpIfCount].self_port=pi->conf.self_port;
if (ListenUdpNode(&(UdpIfs[UdpIfCount]))) {
pi->If=&UdpIfs[UdpIfCount];
UdpIfCount++;
}
} else {
pi->If=&UdpIfs[j];
}
}
}
/*
* UdpNodeUpdate()
*/
static void
UdpNodeUpdate(PhysInfo p)
{
UdpInfo pi = (UdpInfo)p->info;
if (Enabled(&pi->conf.options, UDP_CONF_INCOMING) &&
(!UdpListenUpdateSheduled)) {
/* Set a timer to run UdpListenUpdate(). */
TimerInit(&UdpListenUpdateTimer, "UdpListenUpdate",
0, UdpListenUpdate, NULL);
TimerStart(&UdpListenUpdateTimer);
UdpListenUpdateSheduled = 1;
}
}
/*
* UdpSetCommand()
*/
static int
UdpSetCommand(Context ctx, int ac, char *av[], void *arg)
{
UdpInfo const pi = (UdpInfo) ctx->phys->info;
struct u_range rng;
int port;
switch ((intptr_t)arg) {
case SET_PEERADDR:
case SET_SELFADDR:
if (ac < 1 || ac > 2 || !ParseRange(av[0], &rng, ALLOW_IPV4|ALLOW_IPV6))
return(-1);
if (ac > 1) {
if ((port = atoi(av[1])) < 0 || port > 0xffff)
return(-1);
} else {
port = 0;
}
if ((intptr_t)arg == SET_SELFADDR) {
pi->conf.self_addr = rng.addr;
pi->conf.self_port = port;
} else {
pi->conf.peer_addr = rng;
pi->conf.peer_port = port;
}
break;
case SET_ENABLE:
EnableCommand(ac, av, &pi->conf.options, gConfList);
UdpNodeUpdate(ctx->phys);
break;
case SET_DISABLE:
DisableCommand(ac, av, &pi->conf.options, gConfList);
break;
default:
assert(0);
}
return(0);
}
syntax highlighted by Code2HTML, v. 0.9.1