diff options
Diffstat (limited to 'usr.sbin/ppp/netgraph.c')
-rw-r--r-- | usr.sbin/ppp/netgraph.c | 743 |
1 files changed, 743 insertions, 0 deletions
diff --git a/usr.sbin/ppp/netgraph.c b/usr.sbin/ppp/netgraph.c new file mode 100644 index 0000000..23a575b --- /dev/null +++ b/usr.sbin/ppp/netgraph.c @@ -0,0 +1,743 @@ +/*- + * Copyright (c) 2000 Brian Somers <brian@Awfulhak.org> + * All rights reserved. + * + * 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 THE 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 THE 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. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <netgraph.h> +#include <net/ethernet.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netgraph/ng_ether.h> +#include <netgraph/ng_message.h> +#include <netgraph/ng_socket.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <sys/fcntl.h> +#include <sys/uio.h> +#include <termios.h> +#include <sys/time.h> +#include <unistd.h> + +#include "layer.h" +#include "defs.h" +#include "mbuf.h" +#include "log.h" +#include "timer.h" +#include "lqr.h" +#include "hdlc.h" +#include "throughput.h" +#include "fsm.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "async.h" +#include "descriptor.h" +#include "physical.h" +#include "main.h" +#include "mp.h" +#include "chat.h" +#include "auth.h" +#include "chap.h" +#include "cbcp.h" +#include "datalink.h" +#include "slcompress.h" +#include "iplist.h" +#include "ncpaddr.h" +#include "ipcp.h" +#include "ipv6cp.h" +#include "ncp.h" +#include "filter.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "bundle.h" +#include "id.h" +#include "netgraph.h" + + +struct ngdevice { + struct device dev; /* What struct physical knows about */ + int cs; /* Control socket */ + char hook[NG_HOOKSIZ]; /* Our socket node hook */ +}; + +#define device2ng(d) ((d)->type == NG_DEVICE ? (struct ngdevice *)d : NULL) +#define NG_MSGBUFSZ 4096 +#define NETGRAPH_PREFIX "netgraph:" + +unsigned +ng_DeviceSize(void) +{ + return sizeof(struct ngdevice); +} + +static int +ng_MessageOut(struct ngdevice *dev, const char *data) +{ + char path[NG_PATHSIZ]; + char *fmt; + size_t len; + int pos, dpos; + + /* + * We expect a node path, one or more spaces, a command, one or more + * spaces and an ascii netgraph structure. + */ + data += strspn(data, " \t"); + len = strcspn(data, " \t"); + if (len >= sizeof path) { + log_Printf(LogWARN, "%s: %.*s: Node path too long\n", + dev->dev.name, len, data); + return 0; + } + memcpy(path, data, len); + path[len] = '\0'; + data += len; + + data += strspn(data, " \t"); + len = strcspn(data, " \t"); + for (pos = len; pos >= 0; pos--) + if (data[pos] == '%') + len++; + if ((fmt = alloca(len + 4)) == NULL) { + log_Printf(LogWARN, "%s: alloca(%d) failure... %s\n", + dev->dev.name, len + 4, strerror(errno)); + return 0; + } + + /* + * This is probably a waste of time, but we really don't want to end + * up stuffing unexpected % escapes into the kernel.... + */ + for (pos = dpos = 0; pos < (int)len;) { + if (data[dpos] == '%') + fmt[pos++] = '%'; + fmt[pos++] = data[dpos++]; + } + strcpy(fmt + pos, " %s"); + data += dpos; + + data += strspn(data, " \t"); + if (NgSendAsciiMsg(dev->cs, path, fmt, data) < 0) { + log_Printf(LogDEBUG, "%s: NgSendAsciiMsg (to %s): \"%s\", \"%s\": %s\n", + dev->dev.name, path, fmt, data, strerror(errno)); + return 0; + } + + return 1; +} + +/* + * Get a netgraph message + */ +static ssize_t +ng_MessageIn(struct physical *p, char *buf, size_t sz) +{ + char msgbuf[sizeof(struct ng_mesg) * 2 + NG_MSGBUFSZ]; + struct ngdevice *dev = device2ng(p->handler); + struct ng_mesg *rep = (struct ng_mesg *)msgbuf; + char path[NG_PATHSIZ]; + size_t len; + +#ifdef BROKEN_SELECT + struct timeval t; + fd_set *r; + int ret; + + if (dev->cs < 0) + return 0; + + if ((r = mkfdset()) == NULL) { + log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n"); + return -1; + } + zerofdset(r); + FD_SET(dev->cs, r); + t.tv_sec = t.tv_usec = 0; + ret = select(dev->cs + 1, r, NULL, NULL, &t); + free(r); + + if (ret <= 0) + return 0; +#endif + + if (NgRecvAsciiMsg(dev->cs, rep, sizeof msgbuf, path)) { + log_Printf(LogWARN, "%s: NgRecvAsciiMsg: %s\n", + dev->dev.name, strerror(errno)); + return -1; + } + + /* XXX: Should we check rep->header.version ? */ + + if (sz == 0) + log_Printf(LogWARN, "%s: Unexpected message: %s\n", dev->dev.name, + rep->header.cmdstr); + else { + log_Printf(LogDEBUG, "%s: Received message: %s\n", dev->dev.name, + rep->header.cmdstr); + len = strlen(rep->header.cmdstr); + if (sz > len) + sz = len; + memcpy(buf, rep->header.cmdstr, sz); + } + + return sz; +} + +static ssize_t +ng_Write(struct physical *p, const void *v, size_t n) +{ + struct ngdevice *dev = device2ng(p->handler); + + switch (p->dl->state) { + case DATALINK_DIAL: + case DATALINK_LOGIN: + return ng_MessageOut(dev, v) ? (ssize_t)n : -1; + } + return NgSendData(p->fd, dev->hook, v, n) == -1 ? -1 : (ssize_t)n; +} + +static ssize_t +ng_Read(struct physical *p, void *v, size_t n) +{ + char hook[NG_HOOKSIZ]; + + switch (p->dl->state) { + case DATALINK_DIAL: + case DATALINK_LOGIN: + return ng_MessageIn(p, v, n); + } + + return NgRecvData(p->fd, v, n, hook); +} + +static int +ng_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e) +{ + struct ngdevice *dev = device2ng(p->handler); + int result; + + if (r && dev->cs >= 0 && FD_ISSET(dev->cs, r)) { + FD_CLR(dev->cs, r); + log_Printf(LogTIMER, "%s: fdunset(ctrl) %d\n", p->link.name, dev->cs); + result = 1; + } else + result = 0; + + /* Careful... physical_RemoveFromSet() called us ! */ + + p->handler->removefromset = NULL; + result += physical_RemoveFromSet(p, r, w, e); + p->handler->removefromset = ng_RemoveFromSet; + + return result; +} + +static void +ng_Free(struct physical *p) +{ + struct ngdevice *dev = device2ng(p->handler); + + physical_SetDescriptor(p); + if (dev->cs != -1) + close(dev->cs); + free(dev); +} + +static void +ng_device2iov(struct device *d, struct iovec *iov, int *niov, + int maxiov __unused, int *auxfd, int *nauxfd) +{ + struct ngdevice *dev; + int sz = physical_MaxDeviceSize(); + + iov[*niov].iov_base = d = realloc(d, sz); + if (d == NULL) { + log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz); + AbortProgram(EX_OSERR); + } + iov[*niov].iov_len = sz; + (*niov)++; + + dev = device2ng(d); + *auxfd = dev->cs; + (*nauxfd)++; +} + +static const struct device basengdevice = { + NG_DEVICE, + "netgraph", + 0, + { CD_REQUIRED, DEF_NGCDDELAY }, + NULL, + ng_RemoveFromSet, + NULL, + NULL, + NULL, + NULL, + NULL, + ng_Free, + ng_Read, + ng_Write, + ng_device2iov, + NULL, + NULL, + NULL +}; + +struct device * +ng_iov2device(int type, struct physical *p, struct iovec *iov, int *niov, + int maxiov __unused, int *auxfd, int *nauxfd) +{ + if (type == NG_DEVICE) { + struct ngdevice *dev = (struct ngdevice *)iov[(*niov)++].iov_base; + + dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */ + if (dev == NULL) { + log_Printf(LogALERT, "Failed to allocate memory: %d\n", + (int)(sizeof *dev)); + AbortProgram(EX_OSERR); + } + + if (*nauxfd) { + dev->cs = *auxfd; + (*nauxfd)--; + } else + dev->cs = -1; + + /* Refresh function pointers etc */ + memcpy(&dev->dev, &basengdevice, sizeof dev->dev); + + /* XXX: Are netgraph always synchronous ? */ + physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF); + return &dev->dev; + } + + return NULL; +} + +static int +ng_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n) +{ + struct physical *p = descriptor2physical(d); + struct ngdevice *dev = device2ng(p->handler); + int result; + + switch (p->dl->state) { + case DATALINK_DIAL: + case DATALINK_LOGIN: + if (r) { + FD_SET(dev->cs, r); + log_Printf(LogTIMER, "%s(ctrl): fdset(r) %d\n", p->link.name, dev->cs); + result = 1; + } else + result = 0; + break; + + default: + result = physical_doUpdateSet(d, r, w, e, n, 0); + break; + } + + return result; +} + +static int +ng_IsSet(struct fdescriptor *d, const fd_set *fdset) +{ + struct physical *p = descriptor2physical(d); + struct ngdevice *dev = device2ng(p->handler); + int result; + + result = dev->cs >= 0 && FD_ISSET(dev->cs, fdset); + result += physical_IsSet(d, fdset); + + return result; +} + +static void +ng_DescriptorRead(struct fdescriptor *d, struct bundle *bundle, + const fd_set *fdset) +{ + struct physical *p = descriptor2physical(d); + struct ngdevice *dev = device2ng(p->handler); + + if (dev->cs >= 0 && FD_ISSET(dev->cs, fdset)) + ng_MessageIn(p, NULL, 0); + + if (physical_IsSet(d, fdset)) + physical_DescriptorRead(d, bundle, fdset); +} + +static struct device * +ng_Abandon(struct ngdevice *dev, struct physical *p) +{ + /* Abandon our node construction */ + close(dev->cs); + close(p->fd); + p->fd = -2; /* Nobody else need try.. */ + free(dev); + + return NULL; +} + + +/* + * Populate the ``word'' (of size ``sz'') named ``what'' from ``from'' + * ending with any character from ``sep''. Point ``endp'' at the next + * word. + */ + +#define GETSEGMENT(what, from, sep, endp) \ + getsegment(#what, (what), sizeof(what), from, sep, endp) + +static int +getsegment(const char *what, char *word, size_t sz, const char *from, + const char *sep, const char **endp) +{ + size_t len; + + if ((len = strcspn(from, sep)) == 0) { + log_Printf(LogWARN, "%s name should not be empty !\n", what); + return 0; + } + + if (len >= sz) { + log_Printf(LogWARN, "%s name too long, max %d !\n", what, sz - 1); + return 0; + } + + strncpy(word, from, len); + word[len] = '\0'; + + *endp = from + len; + *endp += strspn(*endp, sep); + + return 1; +} + +struct device * +ng_Create(struct physical *p) +{ + struct sockaddr_ng ngsock; + u_char rbuf[2048]; + struct sockaddr *sock = (struct sockaddr *)&ngsock; + const struct hooklist *hlist; + const struct nodeinfo *ninfo; + const struct linkinfo *nlink; + struct ngdevice *dev; + struct ng_mesg *resp; + struct ngm_mkpeer mkp; + struct ngm_connect ngc; + const char *devp, *endp; + char lasthook[NG_HOOKSIZ]; + char hook[NG_HOOKSIZ]; + char nodetype[NG_TYPESIZ + NG_NODESIZ]; + char modname[NG_TYPESIZ + 3]; + char path[NG_PATHSIZ]; + char *nodename; + int len, sz, done; + unsigned f; + + dev = NULL; + if (p->fd < 0 && !strncasecmp(p->name.full, NETGRAPH_PREFIX, + sizeof NETGRAPH_PREFIX - 1)) { + p->fd--; /* We own the device - change fd */ + + if ((dev = malloc(sizeof *dev)) == NULL) + return NULL; + + loadmodules(LOAD_VERBOSLY, "netgraph", "ng_socket", NULL); + + /* Create a socket node */ + if (ID0NgMkSockNode(NULL, &dev->cs, &p->fd) == -1) { + log_Printf(LogWARN, "Cannot create netgraph socket node: %s\n", + strerror(errno)); + free(dev); + p->fd = -2; + return NULL; + } + + devp = p->name.full + sizeof NETGRAPH_PREFIX - 1; + *lasthook = *path = '\0'; + log_Printf(LogDEBUG, "%s: Opening netgraph device \"%s\"\n", + p->link.name, devp); + done = 0; + + while (*devp != '\0' && !done) { + if (*devp != '[') { + if (*lasthook == '\0') { + log_Printf(LogWARN, "%s: Netgraph devices must start with" + " [nodetype:nodename]\n", p->link.name); + return ng_Abandon(dev, p); + } + + /* Get the hook name of the new node */ + if (!GETSEGMENT(hook, devp, ".[", &endp)) + return ng_Abandon(dev, p); + log_Printf(LogDEBUG, "%s: Got hook \"%s\"\n", p->link.name, hook); + devp = endp; + if (*devp == '\0') { + log_Printf(LogWARN, "%s: Netgraph device must not end with a second" + " hook\n", p->link.name); + return ng_Abandon(dev, p); + } + if (devp[-1] != '[') { + log_Printf(LogWARN, "%s: Expected a [nodetype:nodename] at device" + " pos %d\n", p->link.name, devp - p->link.name - 1); + return ng_Abandon(dev, p); + } + } else { + /* Use lasthook as the hook name */ + strcpy(hook, lasthook); + devp++; + } + + /* We've got ``lasthook'' and ``hook'', get the node type */ + if (!GETSEGMENT(nodetype, devp, "]", &endp)) + return ng_Abandon(dev, p); + log_Printf(LogDEBUG, "%s: Got node \"%s\"\n", p->link.name, nodetype); + + if ((nodename = strchr(nodetype, ':')) != NULL) { + *nodename++ = '\0'; + if (*nodename == '\0' && *nodetype == '\0') { + log_Printf(LogWARN, "%s: Empty [nodetype:nodename] at device" + " pos %d\n", p->link.name, devp - p->link.name - 1); + return ng_Abandon(dev, p); + } + } + + /* Ignore optional colons after nodes */ + devp = *endp == ':' ? endp + 1 : endp; + if (*devp == '.') + devp++; + + if (*lasthook == '\0') { + /* This is the first node in the chain */ + if (nodename == NULL || *nodename == '\0') { + log_Printf(LogWARN, "%s: %s: No initial device nodename\n", + p->link.name, devp); + return ng_Abandon(dev, p); + } + + if (*nodetype != '\0') { + /* Attempt to load the module */ + snprintf(modname, sizeof modname, "ng_%s", nodetype); + log_Printf(LogDEBUG, "%s: Attempting to load %s.ko\n", + p->link.name, modname); + loadmodules(LOAD_QUIETLY, modname, NULL); + } + + snprintf(path, sizeof path, "%s:", nodename); + /* XXX: If we have a node type, ensure it's correct */ + } else { + /* + * Ask for a list of hooks attached to the previous node. If we + * find the one we're interested in, and if it's connected to a + * node of the right type using the correct hook, use that. + * If we find the hook connected to something else, fail. + * If we find no match, mkpeer the new node. + */ + if (*nodetype == '\0') { + log_Printf(LogWARN, "%s: Nodetype missing at device offset %d\n", + p->link.name, + devp - p->name.full + sizeof NETGRAPH_PREFIX - 1); + return ng_Abandon(dev, p); + } + + /* Get a list of node hooks */ + if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS, + NULL, 0) < 0) { + log_Printf(LogWARN, "%s: %s Cannot send a LISTHOOOKS message: %s\n", + p->link.name, path, strerror(errno)); + return ng_Abandon(dev, p); + } + + /* Get our list back */ + resp = (struct ng_mesg *)rbuf; + if (NgRecvMsg(dev->cs, resp, sizeof rbuf, NULL) <= 0) { + log_Printf(LogWARN, "%s: Cannot get netgraph response: %s\n", + p->link.name, strerror(errno)); + return ng_Abandon(dev, p); + } + + hlist = (const struct hooklist *)resp->data; + ninfo = &hlist->nodeinfo; + + log_Printf(LogDEBUG, "List of netgraph node ``%s'' (id %x) hooks:\n", + path, ninfo->id); + + /* look for a hook already attached. */ + for (f = 0; f < ninfo->hooks; f++) { + nlink = &hlist->link[f]; + + log_Printf(LogDEBUG, " Found %s -> %s (type %s)\n", nlink->ourhook, + nlink->peerhook, nlink->nodeinfo.type); + + if (!strcmp(nlink->ourhook, lasthook)) { + if (strcmp(nlink->peerhook, hook) || + strcmp(nlink->nodeinfo.type, nodetype)) { + log_Printf(LogWARN, "%s: hook %s:%s is already in use\n", + p->link.name, nlink->ourhook, path); + return ng_Abandon(dev, p); + } + /* The node is already hooked up nicely.... reuse it */ + break; + } + } + + if (f == ninfo->hooks) { + /* Attempt to load the module */ + snprintf(modname, sizeof modname, "ng_%s", nodetype); + log_Printf(LogDEBUG, "%s: Attempting to load %s.ko\n", + p->link.name, modname); + loadmodules(LOAD_QUIETLY, modname, NULL); + + /* Create (mkpeer) the new node */ + + snprintf(mkp.type, sizeof mkp.type, "%s", nodetype); + snprintf(mkp.ourhook, sizeof mkp.ourhook, "%s", lasthook); + snprintf(mkp.peerhook, sizeof mkp.peerhook, "%s", hook); + + log_Printf(LogDEBUG, "%s: Doing MKPEER %s%s -> %s (type %s)\n", + p->link.name, path, mkp.ourhook, mkp.peerhook, nodetype); + + if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, + NGM_MKPEER, &mkp, sizeof mkp) < 0) { + log_Printf(LogWARN, "%s Cannot create %s netgraph node: %s\n", + path, nodetype, strerror(errno)); + return ng_Abandon(dev, p); + } + } + len = strlen(path); + snprintf(path + len, sizeof path - len, "%s%s", + path[len - 1] == ':' ? "" : ".", lasthook); + } + + /* Get a list of node hooks */ + if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS, + NULL, 0) < 0) { + log_Printf(LogWARN, "%s: %s Cannot send a LISTHOOOKS message: %s\n", + p->link.name, path, strerror(errno)); + return ng_Abandon(dev, p); + } + + /* Get our list back */ + resp = (struct ng_mesg *)rbuf; + if (NgRecvMsg(dev->cs, resp, sizeof rbuf, NULL) <= 0) { + log_Printf(LogWARN, "%s: Cannot get netgraph response: %s\n", + p->link.name, strerror(errno)); + return ng_Abandon(dev, p); + } + + hlist = (const struct hooklist *)resp->data; + ninfo = &hlist->nodeinfo; + + if (*lasthook != '\0' && nodename != NULL && *nodename != '\0' && + strcmp(ninfo->name, nodename) && + NgNameNode(dev->cs, path, "%s", nodename) < 0) { + log_Printf(LogWARN, "%s: %s: Cannot name netgraph node: %s\n", + p->link.name, path, strerror(errno)); + return ng_Abandon(dev, p); + } + + if (!GETSEGMENT(lasthook, devp, " \t.[", &endp)) + return ng_Abandon(dev, p); + log_Printf(LogDEBUG, "%s: Got hook \"%s\"\n", p->link.name, lasthook); + + len = strlen(lasthook); + done = strchr(" \t", devp[len]) ? 1 : 0; + devp = endp; + + if (*devp != '\0') { + if (devp[-1] == '[') + devp--; + } /* else should moan about devp[-1] being '[' ? */ + } + + snprintf(dev->hook, sizeof dev->hook, "%s", lasthook); + + /* Connect the node to our socket node */ + snprintf(ngc.path, sizeof ngc.path, "%s", path); + snprintf(ngc.ourhook, sizeof ngc.ourhook, "%s", dev->hook); + memcpy(ngc.peerhook, ngc.ourhook, sizeof ngc.peerhook); + + log_Printf(LogDEBUG, "Connecting netgraph socket .:%s -> %s.%s\n", + ngc.ourhook, ngc.path, ngc.peerhook); + if (NgSendMsg(dev->cs, ".:", NGM_GENERIC_COOKIE, + NGM_CONNECT, &ngc, sizeof ngc) < 0) { + log_Printf(LogWARN, "Cannot connect %s and socket netgraph " + "nodes: %s\n", path, strerror(errno)); + return ng_Abandon(dev, p); + } + + /* Hook things up so that we monitor dev->cs */ + p->desc.UpdateSet = ng_UpdateSet; + p->desc.IsSet = ng_IsSet; + p->desc.Read = ng_DescriptorRead; + + memcpy(&dev->dev, &basengdevice, sizeof dev->dev); + + } else { + /* See if we're a netgraph socket */ + + sz = sizeof ngsock; + if (getsockname(p->fd, sock, &sz) != -1 && sock->sa_family == AF_NETGRAPH) { + /* + * It's a netgraph node... We can't determine hook names etc, so we + * stay pretty impartial.... + */ + log_Printf(LogPHASE, "%s: Link is a netgraph node\n", p->link.name); + + if ((dev = malloc(sizeof *dev)) == NULL) { + log_Printf(LogWARN, "%s: Cannot allocate an ether device: %s\n", + p->link.name, strerror(errno)); + return NULL; + } + + memcpy(&dev->dev, &basengdevice, sizeof dev->dev); + dev->cs = -1; + *dev->hook = '\0'; + } + } + + if (dev) { + physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF); + return &dev->dev; + } + + return NULL; +} |