/*
* link.c
*
* Written by Archie Cobbs <archie@freebsd.org>
* Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved.
* See ``COPYRIGHT.whistle''
*/
#include "ppp.h"
#include "link.h"
#include "msg.h"
#include "lcp.h"
#include "phys.h"
#include "command.h"
#include "input.h"
#include "ngfunc.h"
#include "util.h"
#include <netgraph.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_socket.h>
#include <netgraph/ng_tee.h>
/*
* DEFINITIONS
*/
/* Set menu options */
enum {
SET_BUNDLE,
SET_FORWARD,
SET_DROP,
SET_CLEAR,
SET_BANDWIDTH,
SET_LATENCY,
SET_ACCMAP,
SET_MRRU,
SET_MRU,
SET_MTU,
SET_FSM_RETRY,
SET_MAX_RETRY,
SET_RETRY_DELAY,
SET_MAX_CHILDREN,
SET_KEEPALIVE,
SET_IDENT,
SET_ACCEPT,
SET_DENY,
SET_ENABLE,
SET_DISABLE,
SET_YES,
SET_NO
};
#define RBUF_SIZE 100
/*
* INTERNAL FUNCTIONS
*/
static int LinkSetCommand(Context ctx, int ac, char *av[], void *arg);
static void LinkMsg(int type, void *cookie);
static void LinkNgDataEvent(int type, void *cookie);
static void LinkReopenTimeout(void *arg);
/*
* GLOBAL VARIABLES
*/
const struct cmdtab LinkSetActionCmds[] = {
{ "bundle {bundle} [{regex}]", "Terminate incomings locally",
LinkSetCommand, NULL, 2, (void *) SET_BUNDLE },
{ "forward {link} [{regex}]", "Forward incomings",
LinkSetCommand, NULL, 2, (void *) SET_FORWARD },
{ "drop [{regex}]", "drop incomings",
LinkSetCommand, NULL, 2, (void *) SET_DROP },
{ "clear", "Clear actions",
LinkSetCommand, NULL, 2, (void *) SET_CLEAR },
{ NULL },
};
const struct cmdtab LinkSetCmds[] = {
{ "action ...", "Set action on incoming",
CMD_SUBMENU, NULL, 2, (void *) LinkSetActionCmds },
{ "bandwidth {bps}", "Link bandwidth",
LinkSetCommand, NULL, 2, (void *) SET_BANDWIDTH },
{ "latency {microsecs}", "Link latency",
LinkSetCommand, NULL, 2, (void *) SET_LATENCY },
{ "accmap {hex-value}", "Accmap value",
LinkSetCommand, NULL, 2, (void *) SET_ACCMAP },
{ "mrru {value}", "Link MRRU value",
LinkSetCommand, NULL, 2, (void *) SET_MRRU },
{ "mru {value}", "Link MRU value",
LinkSetCommand, NULL, 2, (void *) SET_MRU },
{ "mtu {value}", "Link MTU value",
LinkSetCommand, NULL, 2, (void *) SET_MTU },
{ "fsm-timeout {seconds}", "FSM retry timeout",
LinkSetCommand, NULL, 2, (void *) SET_FSM_RETRY },
{ "max-redial {num}", "Max connect attempts",
LinkSetCommand, NULL, 2, (void *) SET_MAX_RETRY },
{ "redial-delay {num}", "Delay between connect attempts",
LinkSetCommand, NULL, 2, (void *) SET_RETRY_DELAY },
{ "max-children {num}", "Max number of children",
LinkSetCommand, NULL, 2, (void *) SET_MAX_CHILDREN },
{ "keep-alive {secs} {max}", "LCP echo keep-alives",
LinkSetCommand, NULL, 2, (void *) SET_KEEPALIVE },
{ "ident {string}", "LCP ident string",
LinkSetCommand, NULL, 2, (void *) SET_IDENT },
{ "accept {opt ...}", "Accept option",
LinkSetCommand, NULL, 2, (void *) SET_ACCEPT },
{ "deny {opt ...}", "Deny option",
LinkSetCommand, NULL, 2, (void *) SET_DENY },
{ "enable {opt ...}", "Enable option",
LinkSetCommand, NULL, 2, (void *) SET_ENABLE },
{ "disable {opt ...}", "Disable option",
LinkSetCommand, NULL, 2, (void *) SET_DISABLE },
{ "yes {opt ...}", "Enable and accept option",
LinkSetCommand, NULL, 2, (void *) SET_YES },
{ "no {opt ...}", "Disable and deny option",
LinkSetCommand, NULL, 2, (void *) SET_NO },
{ NULL },
};
/*
* INTERNAL VARIABLES
*/
static struct confinfo gConfList[] = {
{ 0, LINK_CONF_INCOMING, "incoming" },
{ 1, LINK_CONF_PAP, "pap" },
{ 1, LINK_CONF_CHAPMD5, "chap-md5" },
{ 1, LINK_CONF_CHAPMSv1, "chap-msv1" },
{ 1, LINK_CONF_CHAPMSv2, "chap-msv2" },
{ 1, LINK_CONF_EAP, "eap" },
{ 1, LINK_CONF_ACFCOMP, "acfcomp" },
{ 1, LINK_CONF_PROTOCOMP, "protocomp" },
{ 0, LINK_CONF_MSDOMAIN, "keep-ms-domain"},
{ 0, LINK_CONF_MAGICNUM, "magicnum" },
{ 0, LINK_CONF_PASSIVE, "passive" },
{ 0, LINK_CONF_CHECK_MAGIC, "check-magic" },
{ 0, LINK_CONF_NO_ORIG_AUTH, "no-orig-auth" },
{ 0, LINK_CONF_CALLBACK, "callback" },
{ 0, LINK_CONF_MULTILINK, "multilink" },
{ 1, LINK_CONF_SHORTSEQ, "shortseq" },
{ 0, LINK_CONF_TIMEREMAIN, "time-remain" },
{ 0, LINK_CONF_PEER_AS_CALLING, "peer-as-calling" },
{ 0, LINK_CONF_REPORT_MAC, "report-mac" },
{ 0, LINK_CONF_REMOVE_TEE, "remove-tee" },
{ 0, 0, NULL },
};
int gLinksCsock = -1; /* Socket node control socket */
int gLinksDsock = -1; /* Socket node data socket */
EventRef gLinksDataEvent;
int
LinksInit(void)
{
char name[NG_NODESIZ];
/* Create a netgraph socket node */
snprintf(name, sizeof(name), "mpd%d-lso", gPid);
if (NgMkSockNode(name, &gLinksCsock, &gLinksDsock) < 0) {
Perror("LinksInit(): can't create %s node", NG_SOCKET_NODE_TYPE);
return(-1);
}
(void) fcntl(gLinksCsock, F_SETFD, 1);
(void) fcntl(gLinksDsock, F_SETFD, 1);
/* Listen for happenings on our node */
EventRegister(&gLinksDataEvent, EVENT_READ,
gLinksDsock, EVENT_RECURRING, LinkNgDataEvent, NULL);
return (0);
}
void
LinksShutdown(void)
{
close(gLinksCsock);
gLinksCsock = -1;
EventUnRegister(&gLinksDataEvent);
close(gLinksDsock);
gLinksDsock = -1;
}
/*
* LinkOpenCmd()
*/
int
LinkOpenCmd(Context ctx)
{
if (ctx->lnk->tmpl)
Error("impossible to open template");
RecordLinkUpDownReason(NULL, ctx->lnk, 1, STR_MANUALLY, NULL);
LinkOpen(ctx->lnk);
return (0);
}
/*
* LinkCloseCmd()
*/
int
LinkCloseCmd(Context ctx)
{
if (ctx->lnk->tmpl)
Error("impossible to close template");
RecordLinkUpDownReason(NULL, ctx->lnk, 0, STR_MANUALLY, NULL);
LinkClose(ctx->lnk);
return (0);
}
/*
* LinkOpen()
*/
void
LinkOpen(Link l)
{
REF(l);
MsgSend(&l->msgs, MSG_OPEN, l);
}
/*
* LinkClose()
*/
void
LinkClose(Link l)
{
REF(l);
MsgSend(&l->msgs, MSG_CLOSE, l);
}
/*
* LinkUp()
*/
void
LinkUp(Link l)
{
Log(LG_LINK, ("[%s] Link: UP event", l->name));
l->originate = PhysGetOriginate(l);
Log(LG_PHYS2, ("[%s] Link: origination is %s",
l->name, LINK_ORIGINATION(l->originate)));
LcpUp(l);
}
/*
* LinkDown()
*/
void
LinkDown(Link l)
{
Log(LG_LINK, ("[%s] Link: DOWN event", l->name));
if (OPEN_STATE(l->lcp.fsm.state)) {
if (((l->conf.max_redial != 0) && (l->num_redial >= l->conf.max_redial)) ||
gShutdownInProgress) {
if (l->conf.max_redial >= 0) {
Log(LG_LINK, ("[%s] Link: giving up after %d reconnection attempts",
l->name, l->num_redial));
}
if (!l->stay)
l->die = 1;
LcpClose(l);
LcpDown(l);
} else {
int delay = l->conf.redial_delay + ((random() ^ l->id ^ gPid) & 3);
TimerStop(&l->openTimer);
TimerInit(&l->openTimer, "PhysOpen",
delay * SECONDS, LinkReopenTimeout, l);
TimerStart(&l->openTimer);
LcpDown(l);
l->num_redial++;
Log(LG_LINK, ("[%s] Link: reconnection attempt %d in %d seconds",
l->name, l->num_redial, delay));
}
} else {
if (!l->stay)
l->die = 1;
LcpDown(l);
}
}
/*
* LinkReopenTimeout()
*/
static void
LinkReopenTimeout(void *arg)
{
Link const l = (Link)arg;
if (gShutdownInProgress) {
LcpClose(l);
return;
}
Log(LG_LINK, ("[%s] Link: reconnection attempt %d",
l->name, l->num_redial));
RecordLinkUpDownReason(NULL, l, 1, STR_REDIAL, NULL);
PhysOpen(l);
}
/*
* LinkMsg()
*
* Deal with incoming message to this link
*/
static void
LinkMsg(int type, void *arg)
{
Link l = (Link)arg;
if (l->dead) {
UNREF(l);
return;
}
Log(LG_LINK, ("[%s] Link: %s event", l->name, MsgName(type)));
switch (type) {
case MSG_OPEN:
l->num_redial = 0;
LcpOpen(l);
break;
case MSG_CLOSE:
TimerStop(&l->openTimer);
LcpClose(l);
break;
case MSG_SHUTDOWN:
LinkShutdown(l);
break;
default:
assert(FALSE);
}
UNREF(l);
}
/*
* LinkCreate()
*/
int
LinkCreate(Context ctx, int ac, char *av[], void *arg)
{
Link l, lt = NULL;
PhysType pt = NULL;
u_char tmpl = 0;
u_char stay = 0;
int k;
RESETREF(ctx->lnk, NULL);
RESETREF(ctx->bund, NULL);
RESETREF(ctx->rep, NULL);
if (ac < 1)
return(-1);
if (strcmp(av[0], "template") == 0) {
tmpl = 1;
stay = 1;
} else if (strcmp(av[0], "static") == 0)
stay = 1;
if (ac != stay + 2)
return(-1);
if (strlen(av[0 + stay]) >= (LINK_MAX_NAME - tmpl * 5))
Error("Link name \"%s\" is too long", av[0 + stay]);
/* See if link name already taken */
if ((l = LinkFind(av[0 + stay])) != NULL)
Error("Link \"%s\" already exists", av[0 + stay]);
for (k = 0; (pt = gPhysTypes[k]); k++) {
if (!strcmp(pt->name, av[0 + stay]))
Error("Name \"%s\" is reserved by device type", av[0 + stay]);
}
/* Locate type */
for (k = 0; (pt = gPhysTypes[k]); k++) {
if (!strcmp(pt->name, av[1 + stay]))
break;
}
if (pt != NULL) {
if (!pt->tmpl && tmpl)
Error("Link type \"%s\" does not support templating", av[1 + stay]);
} else {
/* See if template name specified */
if ((lt = LinkFind(av[1 + stay])) == NULL)
Error("Link template \"%s\" not found", av[1 + tmpl]);
if (!lt->tmpl)
Error("Link \"%s\" is not a template", av[1 + stay]);
}
/* Create and initialize new link */
if (lt) {
l = LinkInst(lt, av[0 + stay], tmpl, stay);
} else {
l = Malloc(MB_LINK, sizeof(*l));
strlcpy(l->name, av[0 + stay], sizeof(l->name));
l->type = pt;
l->tmpl = tmpl;
l->stay = stay;
l->parent = -1;
SLIST_INIT(&l->actions);
/* Initialize link configuration with defaults */
l->conf.mru = LCP_DEFAULT_MRU;
l->conf.mtu = LCP_DEFAULT_MRU;
l->conf.mrru = MP_DEFAULT_MRRU;
l->conf.accmap = 0x000a0000;
l->conf.max_redial = -1;
l->conf.redial_delay = 1;
l->conf.retry_timeout = LINK_DEFAULT_RETRY;
l->conf.max_children = 10000;
l->bandwidth = LINK_DEFAULT_BANDWIDTH;
l->latency = LINK_DEFAULT_LATENCY;
l->upReason = NULL;
l->upReasonValid = 0;
l->downReason = NULL;
l->downReasonValid = 0;
l->tee_removed = 0;
Disable(&l->conf.options, LINK_CONF_CHAPMD5);
Accept(&l->conf.options, LINK_CONF_CHAPMD5);
Disable(&l->conf.options, LINK_CONF_CHAPMSv1);
Deny(&l->conf.options, LINK_CONF_CHAPMSv1);
Disable(&l->conf.options, LINK_CONF_CHAPMSv2);
Accept(&l->conf.options, LINK_CONF_CHAPMSv2);
Disable(&l->conf.options, LINK_CONF_PAP);
Accept(&l->conf.options, LINK_CONF_PAP);
Disable(&l->conf.options, LINK_CONF_EAP);
Accept(&l->conf.options, LINK_CONF_EAP);
Disable(&l->conf.options, LINK_CONF_MSDOMAIN);
Enable(&l->conf.options, LINK_CONF_ACFCOMP);
Accept(&l->conf.options, LINK_CONF_ACFCOMP);
Enable(&l->conf.options, LINK_CONF_PROTOCOMP);
Accept(&l->conf.options, LINK_CONF_PROTOCOMP);
Enable(&l->conf.options, LINK_CONF_MAGICNUM);
Disable(&l->conf.options, LINK_CONF_PASSIVE);
Enable(&l->conf.options, LINK_CONF_CHECK_MAGIC);
Disable(&l->conf.options, LINK_CONF_MULTILINK);
Enable(&l->conf.options, LINK_CONF_SHORTSEQ);
Accept(&l->conf.options, LINK_CONF_SHORTSEQ);
PhysInit(l);
LcpInit(l);
MsgRegister(&l->msgs, LinkMsg);
/* Find a free link pointer */
for (k = 0; k < gNumLinks && gLinks[k] != NULL; k++);
if (k == gNumLinks) /* add a new link pointer */
LengthenArray(&gLinks, sizeof(*gLinks), &gNumLinks, MB_LINK);
l->id = k;
gLinks[k] = l;
REF(l);
}
RESETREF(ctx->lnk, l);
return (0);
}
/*
* LinkDestroy()
*/
int
LinkDestroy(Context ctx, int ac, char *av[], void *arg)
{
Link l;
if (ac > 1)
return(-1);
if (ac == 1) {
if ((l = LinkFind(av[0])) == NULL)
Error("Link \"%s\" not found", av[0]);
} else {
if (ctx->lnk) {
l = ctx->lnk;
} else
Error("No link selected to destroy");
}
if (l->tmpl) {
l->tmpl = 0;
l->stay = 0;
LinkShutdown(l);
} else {
l->stay = 0;
if (l->rep) {
PhysClose(l);
} else if (OPEN_STATE(l->lcp.fsm.state)) {
LcpClose(l);
} else {
l->die = 1; /* Hack! We should do it as we changed l->stay */
LinkShutdownCheck(l, l->lcp.fsm.state);
}
}
return (0);
}
/*
* LinkInst()
*/
Link
LinkInst(Link lt, char *name, int tmpl, int stay)
{
Link l;
int k;
struct linkaction *a, *ap, *at;
/* Create and initialize new link */
l = Mdup(MB_LINK, lt, sizeof(*l));
ap = NULL;
SLIST_INIT(&l->actions);
SLIST_FOREACH(at, <->actions, next) {
a = Mdup(MB_AUTH, at, sizeof(*a));
if (a->regex[0])
regcomp(&a->regexp, a->regex, REG_EXTENDED);
if (!ap)
SLIST_INSERT_HEAD(&l->actions, a, next);
else
SLIST_INSERT_AFTER(ap, a, next);
ap = a;
}
l->tmpl = tmpl;
l->stay = stay;
/* Count link as one more child of parent. */
gChildren++;
lt->children++;
l->parent = lt->id;
l->children = 0;
l->refs = 0;
/* Find a free link pointer */
for (k = 0; k < gNumLinks && gLinks[k] != NULL; k++);
if (k == gNumLinks) /* add a new link pointer */
LengthenArray(&gLinks, sizeof(*gLinks), &gNumLinks, MB_LINK);
l->id = k;
if (name)
strlcpy(l->name, name, sizeof(l->name));
else
snprintf(l->name, sizeof(l->name), "%s-%d", lt->name, k);
gLinks[k] = l;
REF(l);
PhysInst(l, lt);
LcpInst(l, lt);
return (l);
}
void
LinkShutdownCheck(Link l, short state)
{
if (state == ST_INITIAL && l->lcp.auth.acct_thread == NULL &&
l->die && !l->stay && l->state == PHYS_STATE_DOWN) {
REF(l);
MsgSend(&l->msgs, MSG_SHUTDOWN, l);
}
}
/*
* LinkShutdown()
*
*/
void
LinkShutdown(Link l)
{
struct linkaction *a;
Log(LG_LINK, ("[%s] Link: Shutdown", l->name));
/* Late divorce for DoD case */
if (l->bund) {
l->bund->links[l->bundleIndex] = NULL;
l->bund->n_links--;
l->bund = NULL;
}
gLinks[l->id] = NULL;
/* Our parent lost one children */
if (l->parent >= 0) {
gChildren--;
gLinks[l->parent]->children--;
}
/* Our children are orphans */
if (l->children) {
int k;
for (k = 0; k < gNumLinks; k++) {
if (gLinks[k] && gLinks[k]->parent == l->id)
gLinks[k]->parent = -1;
}
}
MsgUnRegister(&l->msgs);
if (l->hook[0])
LinkNgShutdown(l);
PhysShutdown(l);
LcpShutdown(l);
l->dead = 1;
while ((a = SLIST_FIRST(&l->actions)) != NULL) {
SLIST_REMOVE_HEAD(&l->actions, next);
if (a->regex[0])
regfree(&a->regexp);
Freee(a);
}
if (l->upReason)
Freee(l->upReason);
if (l->downReason)
Freee(l->downReason);
MsgUnRegister(&l->msgs);
UNREF(l);
CheckOneShot();
}
/*
* LinkNgInit()
*
* Setup the initial link framework.
*
* Returns -1 if error.
*/
int
LinkNgInit(Link l)
{
struct ngm_mkpeer mp;
struct ngm_name nm;
/* Initialize structures */
memset(&mp, 0, sizeof(mp));
memset(&nm, 0, sizeof(nm));
/* Create TEE node */
strcpy(mp.type, NG_TEE_NODE_TYPE);
snprintf(mp.ourhook, sizeof(mp.ourhook), "l%d", l->id);
strcpy(mp.peerhook, NG_TEE_HOOK_LEFT2RIGHT);
if (NgSendMsg(gLinksCsock, ".:",
NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)) < 0) {
Perror("[%s] can't create %s node at \"%s\"->\"%s\"",
l->name, mp.type, ".:", mp.ourhook);
goto fail;
}
strlcpy(l->hook, mp.ourhook, sizeof(l->hook));
/* Give it a name */
snprintf(nm.name, sizeof(nm.name), "mpd%d-%s-lt", gPid, l->name);
if (NgSendMsg(gLinksCsock, l->hook,
NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
Perror("[%s] can't name %s node \"%s\"",
l->name, NG_TEE_NODE_TYPE, l->hook);
goto fail;
}
/* Get TEE node ID */
if ((l->nodeID = NgGetNodeID(gLinksCsock, l->hook)) == 0) {
Perror("[%s] Cannot get %s node id", l->name, NG_TEE_NODE_TYPE);
goto fail;
};
/* OK */
return(0);
fail:
LinkNgShutdown(l);
return(-1);
}
/*
* LinkNgJoin()
*/
int
LinkNgJoin(Link l)
{
char path[NG_PATHSIZ];
struct ngm_connect cn;
snprintf(path, sizeof(path), "[%lx]:", (u_long)l->nodeID);
memset(&cn, 0, sizeof(cn));
snprintf(cn.path, sizeof(cn.path), "[%lx]:", (u_long)l->bund->nodeID);
strcpy(cn.ourhook, NG_TEE_HOOK_RIGHT);
snprintf(cn.peerhook, sizeof(cn.peerhook), "%s%d",
NG_PPP_HOOK_LINK_PREFIX, l->bundleIndex);
if (NgSendMsg(gLinksCsock, path,
NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
l->name, path, cn.ourhook, cn.path, cn.peerhook);
return(-1);
}
NgFuncDisconnect(gLinksCsock, l->name, path, NG_TEE_HOOK_LEFT2RIGHT);
if (Enabled(&l->conf.options, LINK_CONF_REMOVE_TEE)) {
if (l->stay == 0) {
Log(LG_LINK, ("[%s] Link: Removing ng_tee node", l->name));
NgFuncShutdownNode(gLinksCsock, NG_TEE_NODE_TYPE, path);
l->tee_removed = 1;
} else
Log(LG_LINK, ("[%s] Link: Can't remove ng_tee node on static link",
l->name));
}
return (0);
}
/*
* LinkNgLeave()
*/
int
LinkNgLeave(Link l)
{
char path[NG_PATHSIZ];
struct ngm_connect cn;
/* ng_tee is already removed */
if (l->tee_removed == 1)
return(0);
memset(&cn, 0, sizeof(cn));
snprintf(cn.path, sizeof(cn.path), "[%lx]:", (u_long)l->nodeID);
strcpy(cn.ourhook, l->hook);
strcpy(cn.peerhook, NG_TEE_HOOK_LEFT2RIGHT);
if (NgSendMsg(gLinksCsock, ".:",
NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
l->name, ".:", cn.ourhook, cn.path, cn.peerhook);
return(-1);
}
snprintf(path, sizeof(path), "[%lx]:", (u_long)l->nodeID);
NgFuncDisconnect(gLinksCsock, l->name, path, NG_TEE_HOOK_RIGHT);
return (0);
}
/*
* LinkNgToRep()
*/
int
LinkNgToRep(Link l)
{
char path[NG_PATHSIZ];
struct ngm_connect cn;
/* Connect link to repeater */
snprintf(path, sizeof(path), "[%lx]:", (u_long)l->nodeID);
strcpy(cn.ourhook, NG_TEE_HOOK_RIGHT);
if (!PhysGetUpperHook(l, cn.path, cn.peerhook)) {
Log(LG_PHYS, ("[%s] Link: can't get repeater hook", l->name));
return (-1);
}
if (NgSendMsg(gLinksCsock, path,
NGM_GENERIC_COOKIE, NGM_CONNECT, &cn, sizeof(cn)) < 0) {
Perror("[%s] can't connect \"%s\"->\"%s\" and \"%s\"->\"%s\"",
l->name, path, cn.ourhook, cn.path, cn.peerhook);
return(-1);
}
/* Shutdown link tee node */
NgFuncShutdownNode(gLinksCsock, l->name, path);
l->hook[0] = 0;
return (0);
}
/*
* LinkNgShutdown()
*/
void
LinkNgShutdown(Link l)
{
if (l->hook[0])
NgFuncShutdownNode(gLinksCsock, l->name, l->hook);
l->hook[0] = 0;
}
/*
* LinkNgDataEvent()
*/
static void
LinkNgDataEvent(int type, void *cookie)
{
Link l;
Bund b;
u_char *buf;
u_int16_t proto;
int ptr;
Mbuf bp;
struct sockaddr_ng naddr;
socklen_t nsize;
char *name, *rest;
int id, num = 0;
/* Read all available packets */
while (1) {
if (num > 20)
return;
bp = mballoc(4096);
buf = MBDATA(bp);
/* Read data */
nsize = sizeof(naddr);
if ((bp->cnt = recvfrom(gLinksDsock, buf, MBSPACE(bp), MSG_DONTWAIT, (struct sockaddr *)&naddr, &nsize)) < 0) {
mbfree(bp);
if (errno == EAGAIN)
return;
Perror("Link: Link socket read error");
return;
}
num++;
name = naddr.sg_data;
switch (name[0]) {
case 'l':
name++;
id = strtol(name, &rest, 10);
if (rest[0] != 0 || !gLinks[id]) {
Log(LG_ERR, ("Link: Packet from unexisting link \"%s\"",
name));
mbfree(bp);
continue;
}
if (gLinks[id]->dead) {
Log(LG_LINK, ("Link: Packet from dead link \"%s\"", name));
mbfree(bp);
continue;
}
l = gLinks[id];
/* Extract protocol */
ptr = 0;
if ((buf[0] == 0xff) && (buf[1] == 0x03))
ptr = 2;
proto = buf[ptr++];
if ((proto & 0x01) == 0)
proto = (proto << 8) + buf[ptr++];
if (MBLEN(bp) <= ptr) {
LogDumpBp(LG_FRAME|LG_ERR, bp,
"[%s] rec'd truncated %zu bytes frame from link",
l->name, MBLEN(bp));
mbfree(bp);
continue;
}
/* Debugging */
LogDumpBp(LG_FRAME, bp,
"[%s] rec'd %zu bytes frame from link proto=0x%04x",
l->name, MBLEN(bp), proto);
bp = mbadj(bp, ptr);
/* Input frame */
InputFrame(l->bund, l, proto, bp);
break;
case 'b':
case 'i':
case 'o':
case '4':
case '6':
name++;
id = strtol(name, &rest, 10);
if (rest[0] != 0 || !gBundles[id]) {
Log(LG_ERR, ("Link: Packet from unexisting bundle \"%s\"",
name));
mbfree(bp);
continue;
}
if (gBundles[id]->dead) {
Log(LG_LINK, ("Link: Packet from dead bundle \"%s\"", name));
mbfree(bp);
continue;
}
b = gBundles[id];
/* A PPP frame from the bypass hook? */
if (naddr.sg_data[0] == 'b') {
Link l;
u_int16_t linkNum, proto;
if (MBLEN(bp) <= 4) {
LogDumpBp(LG_FRAME|LG_ERR, bp,
"[%s] rec'd truncated %zu bytes frame",
b->name, MBLEN(bp));
continue;
}
/* Extract link number and protocol */
bp = mbread(bp, &linkNum, 2);
linkNum = ntohs(linkNum);
bp = mbread(bp, &proto, 2);
proto = ntohs(proto);
/* Debugging */
LogDumpBp(LG_FRAME, bp,
"[%s] rec'd %zu bytes bypass frame link=%d proto=0x%04x",
b->name, MBLEN(bp), (int16_t)linkNum, proto);
/* Set link */
assert(linkNum == NG_PPP_BUNDLE_LINKNUM || linkNum < NG_PPP_MAX_LINKS);
if (linkNum != NG_PPP_BUNDLE_LINKNUM)
l = b->links[linkNum];
else
l = NULL;
InputFrame(b, l, proto, bp);
continue;
}
/* Debugging */
LogDumpBp(LG_FRAME, bp,
"[%s] rec'd %zu bytes frame on %s hook", b->name, MBLEN(bp), naddr.sg_data);
#ifndef USE_NG_TCPMSS
/* A snooped, outgoing TCP SYN frame */
if (naddr.sg_data[0] == 'o') {
IfaceCorrectMSS(bp, MAXMSS(b->iface.mtu));
naddr.sg_data[0] = 'i';
NgFuncWriteFrame(gLinksDsock, naddr.sg_data, b->name, bp);
continue;
}
/* A snooped, incoming TCP SYN frame */
if (naddr.sg_data[0] == 'i') {
IfaceCorrectMSS(bp, MAXMSS(b->iface.mtu));
naddr.sg_data[0] = 'o';
NgFuncWriteFrame(gLinksDsock, naddr.sg_data, b->name, bp);
continue;
}
#endif
/* A snooped, outgoing IP frame */
if (naddr.sg_data[0] == '4') {
IfaceListenInput(b, PROTO_IP, bp);
continue;
}
/* A snooped, outgoing IPv6 frame */
if (naddr.sg_data[0] == '6') {
IfaceListenInput(b, PROTO_IPV6, bp);
continue;
}
break;
default:
Log(LG_ERR, ("Link: Packet from unknown hook \"%s\"",
name));
mbfree(bp);
}
}
}
/*
* LinkFind()
*
* Find a link structure
*/
Link
LinkFind(const char *name)
{
int k;
k = gNumLinks;
if ((sscanf(name, "[%x]", &k) != 1) || (k < 0) || (k >= gNumLinks)) {
/* Find link */
for (k = 0;
k < gNumLinks && (gLinks[k] == NULL ||
strcmp(gLinks[k]->name, name));
k++);
};
if (k == gNumLinks) {
return (NULL);
}
return (gLinks[k]);
}
/*
* LinkCommand()
*/
int
LinkCommand(Context ctx, int ac, char *av[], void *arg)
{
Link l;
int k;
if (ac > 1)
return (-1);
if (ac == 0) {
Printf("Defined links:\r\n");
for (k = 0; k < gNumLinks; k++) {
if ((l = gLinks[k]) != NULL) {
if (l && l->bund)
Printf("\t%-15s%s\r\n",
l->name, l->bund->name);
else if (l->rep)
Printf("\t%-15s%s\r\n",
l->name, l->rep->name);
else
Printf("\t%s\r\n",
l->name);
}
}
return (0);
}
if ((l = LinkFind(av[0])) == NULL) {
RESETREF(ctx->lnk, NULL);
RESETREF(ctx->bund, NULL);
RESETREF(ctx->rep, NULL);
Error("Link \"%s\" is not defined", av[0]);
}
/* Change default link and bundle */
RESETREF(ctx->lnk, l);
RESETREF(ctx->bund, l->bund);
RESETREF(ctx->rep, NULL);
return(0);
}
/*
* SessionCommand()
*/
int
SessionCommand(Context ctx, int ac, char *av[], void *arg)
{
int k;
if (ac > 1)
return (-1);
if (ac == 0) {
Printf("Present sessions:\r\n");
for (k = 0; k < gNumLinks; k++) {
if (gLinks[k] && gLinks[k]->session_id[0])
Printf("\t%s\r\n", gLinks[k]->session_id);
}
return (0);
}
/* Find link */
for (k = 0;
k < gNumLinks && (gLinks[k] == NULL ||
strcmp(gLinks[k]->session_id, av[0]));
k++);
if (k == gNumLinks) {
/* Change default link and bundle */
RESETREF(ctx->lnk, NULL);
RESETREF(ctx->bund, NULL);
RESETREF(ctx->rep, NULL);
Error("Session \"%s\" is not found", av[0]);
}
/* Change default link and bundle */
RESETREF(ctx->lnk, gLinks[k]);
RESETREF(ctx->bund, ctx->lnk->bund);
RESETREF(ctx->rep, NULL);
return(0);
}
/*
* AuthnameCommand()
*/
int
AuthnameCommand(Context ctx, int ac, char *av[], void *arg)
{
int k;
if (ac > 2)
return (-1);
if (ac == 0) {
Printf("Present users:\r\n");
for (k = 0; k < gNumLinks; k++) {
if (gLinks[k] && gLinks[k]->lcp.auth.params.authname[0])
Printf("\t%s\r\n", gLinks[k]->lcp.auth.params.authname);
}
return (0);
}
if (ac == 2 && strcasecmp(av[1], "ci") == 0) {
/* Find link */
for (k = 0;
k < gNumLinks && (gLinks[k] == NULL ||
strcasecmp(gLinks[k]->lcp.auth.params.authname, av[0]));
k++);
} else {
/* Find link */
for (k = 0;
k < gNumLinks && (gLinks[k] == NULL ||
strcmp(gLinks[k]->lcp.auth.params.authname, av[0]));
k++);
}
if (k == gNumLinks) {
/* Change default link and bundle */
RESETREF(ctx->lnk, NULL);
RESETREF(ctx->bund, NULL);
RESETREF(ctx->rep, NULL);
Error("User \"%s\" is not found", av[0]);
}
/* Change default link and bundle */
RESETREF(ctx->lnk, gLinks[k]);
RESETREF(ctx->bund, ctx->lnk->bund);
RESETREF(ctx->rep, NULL);
return(0);
}
/*
* RecordLinkUpDownReason()
*
* This is called whenever a reason for the link going up or
* down has just become known. Record this reason so that when
* the link actually goes up or down, we can record it.
*
* If this gets called more than once in the "down" case,
* the first call prevails.
*/
static void
RecordLinkUpDownReason2(Link l, int up, const char *key, const char *fmt, va_list args)
{
char **const cpp = up ? &l->upReason : &l->downReason;
char *buf;
/* First reason overrides later ones */
if (up) {
if (l->upReasonValid) {
return;
} else {
l->upReasonValid = 1;
}
} else {
if (l->downReasonValid) {
return;
} else {
l->downReasonValid = 1;
}
}
/* Allocate buffer if necessary */
if (!*cpp)
*cpp = Malloc(MB_LINK, RBUF_SIZE);
buf = *cpp;
/* Record reason */
if (fmt) {
snprintf(buf, RBUF_SIZE, "%s:", key);
vsnprintf(buf + strlen(buf), RBUF_SIZE - strlen(buf), fmt, args);
} else
strlcpy(buf, key, RBUF_SIZE);
}
void
RecordLinkUpDownReason(Bund b, Link l, int up, const char *key, const char *fmt, ...)
{
va_list args;
int k;
if (l != NULL) {
va_start(args, fmt);
RecordLinkUpDownReason2(l, up, key, fmt, args);
va_end(args);
} else if (b != NULL) {
for (k = 0; k < NG_PPP_MAX_LINKS; k++) {
if (b->links[k]) {
va_start(args, fmt);
RecordLinkUpDownReason2(b->links[k], up, key, fmt, args);
va_end(args);
}
}
}
}
const char *
LinkMatchAction(Link l, int stage, char *login)
{
struct linkaction *a;
a = SLIST_FIRST(&l->actions);
if (!a) {
Log(LG_LINK, ("[%s] Link: No actions defined", l->name));
return (NULL);
}
if (stage == 1) {
if (SLIST_NEXT(a, next) == NULL && a->regex[0] == 0) {
if (a->action == LINK_ACTION_FORWARD) {
Log(LG_LINK, ("[%s] Link: Matched action 'forward \"%s\"'",
l->name, a->arg));
return (a->arg);
}
if (a->action == LINK_ACTION_DROP) {
Log(LG_LINK, ("[%s] Link: Matched action 'drop'",
l->name));
return ("##DROP##");
}
}
return (NULL);
}
SLIST_FOREACH(a, &l->actions, next) {
if (!a->regex[0] || !regexec(&a->regexp, login, 0, NULL, 0))
break;
}
if (a) {
if (a->action == LINK_ACTION_DROP) {
Log(LG_LINK, ("[%s] Link: Matched action 'drop'",
l->name));
return ("##DROP##");
}
if ((stage == 2 && a->action == LINK_ACTION_FORWARD) ||
(stage == 3 && a->action == LINK_ACTION_BUNDLE)) {
Log(LG_LINK, ("[%s] Link: Matched action '%s \"%s\" \"%s\"'",
l->name, (a->action == LINK_ACTION_FORWARD)?"forward":"bundle",
a->arg, a->regex));
return (a->arg);
}
}
return (NULL);
}
/*
* LinkStat()
*/
int
LinkStat(Context ctx, int ac, char *av[], void *arg)
{
Link l = ctx->lnk;
struct linkaction *a;
Printf("Link %s%s:\r\n", l->name, l->tmpl?" (template)":(l->stay?" (static)":""));
Printf("Configuration:\r\n");
Printf("\tDevice type : %s\r\n", l->type?l->type->name:"");
Printf("\tMRU : %d bytes\r\n", l->conf.mru);
Printf("\tMRRU : %d bytes\r\n", l->conf.mrru);
Printf("\tCtrl char map : 0x%08x bytes\r\n", l->conf.accmap);
Printf("\tRetry timeout : %d seconds\r\n", l->conf.retry_timeout);
Printf("\tMax redial : ");
if (l->conf.max_redial < 0)
Printf("no redial\r\n");
else if (l->conf.max_redial == 0)
Printf("unlimited, delay %ds\r\n", l->conf.redial_delay);
else
Printf("%d connect attempts, delay %ds\r\n",
l->conf.max_redial, l->conf.redial_delay);
Printf("\tBandwidth : %d bits/sec\r\n", l->bandwidth);
Printf("\tLatency : %d usec\r\n", l->latency);
Printf("\tKeep-alive : ");
if (l->lcp.fsm.conf.echo_int == 0)
Printf("disabled\r\n");
else
Printf("every %d secs, timeout %d\r\n",
l->lcp.fsm.conf.echo_int, l->lcp.fsm.conf.echo_max);
Printf("\tIdent string : \"%s\"\r\n", l->conf.ident ? l->conf.ident : "");
if (l->tmpl)
Printf("\tMax children : %d\r\n", l->conf.max_children);
Printf("Link incoming actions:\r\n");
SLIST_FOREACH(a, &l->actions, next) {
Printf("\t%s\t%s\t%s\r\n",
(a->action == LINK_ACTION_FORWARD)?"Forward":
(a->action == LINK_ACTION_BUNDLE)?"Bundle":"Drop",
a->arg, a->regex);
}
Printf("Link level options:\r\n");
OptStat(ctx, &l->conf.options, gConfList);
Printf("Link state:\r\n");
if (l->tmpl)
Printf("\tChildren : %d\r\n", l->children);
else {
Printf("\tState : %s\r\n", gPhysStateNames[l->state]);
Printf("\tSession Id : %s\r\n", l->session_id);
Printf("\tPeer ident : %s\r\n", l->lcp.peer_ident);
if (l->state == PHYS_STATE_UP)
Printf("\tSession time : %ld seconds\r\n", (long int)(time(NULL) - l->last_up));
}
if (!l->tmpl) {
Printf("Up/Down stats:\r\n");
if (l->downReason && (!l->downReasonValid))
Printf("\tDown Reason : %s\r\n", l->downReason);
if (l->upReason)
Printf("\tUp Reason : %s\r\n", l->upReason);
if (l->downReason && l->downReasonValid)
Printf("\tDown Reason : %s\r\n", l->downReason);
if (l->bund) {
LinkUpdateStats(l);
Printf("Traffic stats:\r\n");
Printf("\tInput octets : %llu\r\n", (unsigned long long)l->stats.recvOctets);
Printf("\tInput frames : %llu\r\n", (unsigned long long)l->stats.recvFrames);
Printf("\tOutput octets : %llu\r\n", (unsigned long long)l->stats.xmitOctets);
Printf("\tOutput frames : %llu\r\n", (unsigned long long)l->stats.xmitFrames);
Printf("\tBad protocols : %llu\r\n", (unsigned long long)l->stats.badProtos);
Printf("\tRunts : %llu\r\n", (unsigned long long)l->stats.runts);
Printf("\tDup fragments : %llu\r\n", (unsigned long long)l->stats.dupFragments);
Printf("\tDrop fragments : %llu\r\n", (unsigned long long)l->stats.dropFragments);
}
}
return(0);
}
/*
* LinkUpdateStats()
*/
void
LinkUpdateStats(Link l)
{
#ifndef NG_PPP_STATS64
struct ng_ppp_link_stat stats;
if (NgFuncGetStats(l->bund, l->bundleIndex, &stats) != -1) {
l->stats.xmitFrames += abs(stats.xmitFrames - l->oldStats.xmitFrames);
l->stats.xmitOctets += abs(stats.xmitOctets - l->oldStats.xmitOctets);
l->stats.recvFrames += abs(stats.recvFrames - l->oldStats.recvFrames);
l->stats.recvOctets += abs(stats.recvOctets - l->oldStats.recvOctets);
l->stats.badProtos += abs(stats.badProtos - l->oldStats.badProtos);
l->stats.runts += abs(stats.runts - l->oldStats.runts);
l->stats.dupFragments += abs(stats.dupFragments - l->oldStats.dupFragments);
l->stats.dropFragments += abs(stats.dropFragments - l->oldStats.dropFragments);
}
l->oldStats = stats;
#else
NgFuncGetStats64(l->bund, l->bundleIndex, &l->stats);
#endif
}
/*
* LinkResetStats()
*/
void
LinkResetStats(Link l)
{
if (l->bund)
NgFuncClrStats(l->bund, l->bundleIndex);
memset(&l->stats, 0, sizeof(l->stats));
#ifndef NG_PPP_STATS64
memset(&l->oldStats, 0, sizeof(l->oldStats));
#endif
}
/*
* LinkSetCommand()
*/
static int
LinkSetCommand(Context ctx, int ac, char *av[], void *arg)
{
Link l = ctx->lnk;
int val, nac = 0;
const char *name;
char *nav[ac];
const char *av2[] = { "chap-md5", "chap-msv1", "chap-msv2" };
/* make "chap" as an alias for all chap-variants, this should keep BC */
switch ((intptr_t)arg) {
case SET_ACCEPT:
case SET_DENY:
case SET_ENABLE:
case SET_DISABLE:
case SET_YES:
case SET_NO:
{
int i = 0;
for ( ; i < ac; i++) {
if (strcasecmp(av[i], "chap") == 0) {
LinkSetCommand(ctx, 3, (char **)av2, arg);
} else {
nav[nac++] = av[i];
}
}
av = nav;
ac = nac;
break;
}
}
switch ((intptr_t)arg) {
case SET_BANDWIDTH:
if (ac != 1)
return(-1);
val = atoi(*av);
if (val <= 0)
Error("[%s] Bandwidth must be positive", l->name);
else if (val > NG_PPP_MAX_BANDWIDTH * 10 * 8) {
l->bandwidth = NG_PPP_MAX_BANDWIDTH * 10 * 8;
Log(LG_ERR, ("[%s] Bandwidth truncated to %d bit/s", l->name,
l->bandwidth));
} else
l->bandwidth = val;
break;
case SET_LATENCY:
if (ac != 1)
return(-1);
val = atoi(*av);
if (val < 0)
Error("[%s] Latency must be not negative", l->name);
else if (val > NG_PPP_MAX_LATENCY * 1000) {
Log(LG_ERR, ("[%s] Latency truncated to %d usec", l->name,
NG_PPP_MAX_LATENCY * 1000));
l->latency = NG_PPP_MAX_LATENCY * 1000;
} else
l->latency = val;
break;
case SET_BUNDLE:
case SET_FORWARD:
case SET_DROP:
{
struct linkaction *n, *a;
if ((ac < 1 && (intptr_t)arg != SET_DROP) || ac > 2)
return(-1);
n = Malloc(MB_LINK, sizeof(struct linkaction));
if ((intptr_t)arg != SET_DROP) {
n->action = ((intptr_t)arg == SET_BUNDLE)?
LINK_ACTION_BUNDLE:LINK_ACTION_FORWARD;
strlcpy(n->arg, av[0], sizeof(n->arg));
if (ac == 2 && av[1][0]) {
strlcpy(n->regex, av[1], sizeof(n->regex));
if (regcomp(&n->regexp, n->regex, REG_EXTENDED)) {
Freee(n);
Error("regexp \"%s\" compilation error", av[1]);
}
}
} else {
n->action = LINK_ACTION_DROP;
if (ac == 1 && av[0][0]) {
strlcpy(n->regex, av[0], sizeof(n->regex));
if (regcomp(&n->regexp, n->regex, REG_EXTENDED)) {
Freee(n);
Error("regexp \"%s\" compilation error", av[0]);
}
}
}
a = SLIST_FIRST(&ctx->lnk->actions);
if (a) {
while (SLIST_NEXT(a, next))
a = SLIST_NEXT(a, next);
SLIST_INSERT_AFTER(a, n, next);
} else {
SLIST_INSERT_HEAD(&ctx->lnk->actions, n, next);
}
}
break;
case SET_CLEAR:
{
struct linkaction *a;
if (ac != 0)
return(-1);
while ((a = SLIST_FIRST(&l->actions)) != NULL) {
SLIST_REMOVE_HEAD(&l->actions, next);
if (a->regex[0])
regfree(&a->regexp);
Freee(a);
}
}
break;
case SET_MRU:
case SET_MTU:
if (ac != 1)
return(-1);
val = atoi(*av);
name = ((intptr_t)arg == SET_MTU) ? "MTU" : "MRU";
if (val < LCP_MIN_MRU)
Error("min %s is %d", name, LCP_MIN_MRU);
else if (l->type && (val > l->type->mru)) {
Error("max %s on type \"%s\" links is %d",
name, l->type->name, l->type->mru);
} else if ((intptr_t)arg == SET_MTU)
l->conf.mtu = val;
else
l->conf.mru = val;
break;
case SET_MRRU:
if (ac != 1)
return(-1);
val = atoi(*av);
if (val < MP_MIN_MRRU)
Error("min MRRU is %d", MP_MIN_MRRU);
else if (val > MP_MAX_MRRU)
Error("max MRRU is %d", MP_MAX_MRRU);
else
l->conf.mrru = val;
break;
case SET_FSM_RETRY:
if (ac != 1)
return(-1);
val = atoi(*av);
if (val < 1 || val > 10) {
Error("incorrect fsm-timeout value %d", val);
} else {
l->conf.retry_timeout = val;
}
break;
case SET_MAX_RETRY:
if (ac != 1)
return(-1);
l->conf.max_redial = atoi(*av);
break;
case SET_RETRY_DELAY:
if (ac != 1)
return(-1);
l->conf.redial_delay = atoi(*av);
if (l->conf.redial_delay < 1)
l->conf.redial_delay = 1;
break;
case SET_MAX_CHILDREN:
if (ac != 1)
return(-1);
if (!l->tmpl)
Error("applicable only to templates");
val = atoi(*av);
if (val < 0 || val > 100000)
Error("incorrect value %d", val);
l->conf.max_children = val;
break;
case SET_ACCMAP:
if (ac != 1)
return(-1);
sscanf(*av, "%x", &val);
l->conf.accmap = val;
break;
case SET_KEEPALIVE:
if (ac != 2)
return(-1);
l->lcp.fsm.conf.echo_int = atoi(av[0]);
l->lcp.fsm.conf.echo_max = atoi(av[1]);
break;
case SET_IDENT:
if (ac != 1)
return(-1);
if (l->conf.ident != NULL) {
Freee(l->conf.ident);
l->conf.ident = NULL;
}
if (*av[0] != '\0')
strcpy(l->conf.ident = Malloc(MB_LINK, strlen(av[0]) + 1), av[0]);
break;
case SET_ACCEPT:
AcceptCommand(ac, av, &l->conf.options, gConfList);
if (ctx->lnk->type->update)
(ctx->lnk->type->update)(ctx->lnk);
break;
case SET_DENY:
DenyCommand(ac, av, &l->conf.options, gConfList);
if (ctx->lnk->type->update)
(ctx->lnk->type->update)(ctx->lnk);
break;
case SET_ENABLE:
EnableCommand(ac, av, &l->conf.options, gConfList);
if (ctx->lnk->type->update)
(ctx->lnk->type->update)(ctx->lnk);
break;
case SET_DISABLE:
DisableCommand(ac, av, &l->conf.options, gConfList);
if (ctx->lnk->type->update)
(ctx->lnk->type->update)(ctx->lnk);
break;
case SET_YES:
YesCommand(ac, av, &l->conf.options, gConfList);
if (ctx->lnk->type->update)
(ctx->lnk->type->update)(ctx->lnk);
break;
case SET_NO:
NoCommand(ac, av, &l->conf.options, gConfList);
if (ctx->lnk->type->update)
(ctx->lnk->type->update)(ctx->lnk);
break;
default:
assert(0);
}
return(0);
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>