/*
* pptp.c
*
* Written by Archie Cobbs <archie@freebsd.org>
* Copyright (c) 1998-1999 Whistle Communications, Inc. All rights reserved.
* See ``COPYRIGHT.whistle''
*/
#include "ppp.h"
#include "phys.h"
#include "mbuf.h"
#include "ngfunc.h"
#include "pptp.h"
#include "pptp_ctrl.h"
#include "log.h"
#include "util.h"
#include <net/ethernet.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_socket.h>
#include <netgraph/ng_ksocket.h>
#include <netgraph/ng_pptpgre.h>
#include <netgraph.h>
/*
* DEFINITIONS
*/
#define PPTP_MRU PPTP_MTU
#define PPTP_CALL_MIN_BPS 56000
#define PPTP_CALL_MAX_BPS 64000
struct pptptun {
struct u_addr self_addr; /* Current self IP address */
struct u_addr peer_addr; /* Current peer IP address */
ng_ID_t node_id;
int refs;
};
typedef struct pptptun *PptpTun;
struct pptpinfo {
struct {
struct u_addr self_addr; /* self IP address */
struct u_range peer_addr; /* Peer IP addresses allowed */
in_port_t self_port; /* self port */
in_port_t peer_port; /* Peer port required (or zero) */
struct optinfo options;
char callingnum[64]; /* PPTP phone number to use */
char callednum[64]; /* PPTP phone number to use */
char *fqdn_peer_addr; /* FQDN Peer address */
} conf;
void *listener; /* Listener pointer */
struct u_addr self_addr; /* Current self IP address */
struct u_addr peer_addr; /* Current peer IP address */
char peer_iface[IFNAMSIZ]; /* Peer iface */
u_char peer_mac_addr[6]; /* Peer MAC address */
in_port_t peer_port; /* Current peer port */
u_char originate; /* Call originated locally */
u_char outcall; /* Call is outgoing vs. incoming */
u_char sync; /* Call is sync vs. async */
u_int16_t cid; /* call id */
PptpTun tun;
struct pptpctrlinfo cinfo;
char callingnum[64]; /* PPTP phone number to use */
char callednum[64]; /* PPTP phone number to use */
};
typedef struct pptpinfo *PptpInfo;
/* Set menu options */
enum {
SET_SELFADDR,
SET_PEERADDR,
SET_CALLINGNUM,
SET_CALLEDNUM,
SET_ENABLE,
SET_DISABLE
};
/* Binary options */
enum {
PPTP_CONF_OUTCALL, /* when originating, calls are "outgoing" */
PPTP_CONF_DELAYED_ACK, /* enable delayed receive ack algorithm */
PPTP_CONF_ALWAYS_ACK, /* include ack with all outgoing data packets */
PPTP_CONF_RESOLVE_ONCE, /* Only once resolve peer_addr */
#if NGM_PPTPGRE_COOKIE >= 1082548365
PPTP_CONF_WINDOWING /* control (stupid) windowing algorithm */
#endif
};
/*
* INTERNAL FUNCTIONS
*/
static int PptpTInit(void);
static void PptpTShutdown(void);
static int PptpInit(Link l);
static int PptpInst(Link l, Link lt);
static void PptpOpen(Link l);
static void PptpClose(Link l);
static void PptpShutdown(Link l);
static void PptpStat(Context ctx);
static int PptpOriginated(Link l);
static int PptpIsSync(Link l);
static int PptpSetAccm(Link l, u_int32_t xmit, u_int32_t recv);
static int PptpSetCallingNum(Link l, void *buf);
static int PptpSetCalledNum(Link l, void *buf);
static int PptpSelfName(Link l, void *buf, size_t buf_len);
static int PptpPeerName(Link l, void *buf, size_t buf_len);
static int PptpSelfAddr(Link l, void *buf, size_t buf_len);
static int PptpPeerAddr(Link l, void *buf, size_t buf_len);
static int PptpPeerPort(Link l, void *buf, size_t buf_len);
static int PptpPeerMacAddr(Link l, void *buf, size_t buf_len);
static int PptpPeerIface(Link l, void *buf, size_t buf_len);
static int PptpCallingNum(Link l, void *buf, size_t buf_len);
static int PptpCalledNum(Link l, void *buf, size_t buf_len);
static int PptpOriginate(Link l);
static void PptpDoClose(Link l);
static void PptpUnhook(Link l);
static void PptpResult(void *cookie, const char *errmsg, int frameType);
static void PptpSetLinkInfo(void *cookie, u_int32_t sa, u_int32_t ra);
static void PptpCancel(void *cookie);
static int PptpHookUp(Link l);
static void PptpListenUpdate(Link l);
static struct pptplinkinfo PptpIncoming(struct pptpctrlinfo *cinfo,
struct u_addr *self, struct u_addr *peer, in_port_t port, int bearType,
const char *callingNum,
const char *calledNum,
const char *subAddress);
static struct pptplinkinfo PptpOutgoing(struct pptpctrlinfo *cinfo,
struct u_addr *self, struct u_addr *peer, in_port_t port, int bearType,
int frameType, int minBps, int maxBps,
const char *calledNum,
const char *subAddress);
static struct pptplinkinfo PptpPeerCall(struct pptpctrlinfo *cinfo,
struct u_addr *self, struct u_addr *peer, in_port_t port, int incoming,
const char *callingNum,
const char *calledNum,
const char *subAddress);
static int PptpSetCommand(Context ctx, int ac, char *av[], void *arg);
static int PptpTunEQ(struct ghash *g, const void *item1, const void *item2);
static u_int32_t PptpTunHash(struct ghash *g, const void *item);
/*
* GLOBAL VARIABLES
*/
const struct phystype gPptpPhysType = {
.name = "pptp",
.descr = "Point-to-Point Tunneling Protocol",
.mtu = PPTP_MTU,
.mru = PPTP_MRU,
.tmpl = 1,
.tinit = PptpTInit,
.tshutdown = PptpTShutdown,
.init = PptpInit,
.inst = PptpInst,
.open = PptpOpen,
.close = PptpClose,
.update = PptpListenUpdate,
.shutdown = PptpShutdown,
.showstat = PptpStat,
.originate = PptpOriginated,
.issync = PptpIsSync,
.setaccm = PptpSetAccm,
.setcallingnum = PptpSetCallingNum,
.setcallednum = PptpSetCalledNum,
.selfname = PptpSelfName,
.peername = PptpPeerName,
.selfaddr = PptpSelfAddr,
.peeraddr = PptpPeerAddr,
.peerport = PptpPeerPort,
.peermacaddr = PptpPeerMacAddr,
.peeriface = PptpPeerIface,
.callingnum = PptpCallingNum,
.callednum = PptpCalledNum,
};
const struct cmdtab PptpSetCmds[] = {
{ "self {ip} [{port}]", "Set local IP address",
PptpSetCommand, NULL, 2, (void *) SET_SELFADDR },
{ "peer {ip} [{port}]", "Set remote IP address",
PptpSetCommand, NULL, 2, (void *) SET_PEERADDR },
{ "callingnum {number}", "Set calling PPTP telephone number",
PptpSetCommand, NULL, 2, (void *) SET_CALLINGNUM },
{ "callednum {number}", "Set called PPTP telephone number",
PptpSetCommand, NULL, 2, (void *) SET_CALLEDNUM },
{ "enable [opt ...]", "Enable option",
PptpSetCommand, NULL, 2, (void *) SET_ENABLE },
{ "disable [opt ...]", "Disable option",
PptpSetCommand, NULL, 2, (void *) SET_DISABLE },
{ NULL },
};
/*
* INTERNAL VARIABLES
*/
static struct confinfo gConfList[] = {
{ 0, PPTP_CONF_OUTCALL, "outcall" },
{ 0, PPTP_CONF_DELAYED_ACK, "delayed-ack" },
{ 0, PPTP_CONF_ALWAYS_ACK, "always-ack" },
{ 0, PPTP_CONF_RESOLVE_ONCE, "resolve-once" },
#if NGM_PPTPGRE_COOKIE >= 1082548365
{ 0, PPTP_CONF_WINDOWING, "windowing" },
#endif
{ 0, 0, NULL },
};
struct ghash *gPptpTuns;
/*
* PptpTInit()
*/
static int
PptpTInit(void)
{
if ((gPptpTuns = ghash_create(NULL, 0, 0, MB_PHYS, PptpTunHash, PptpTunEQ, NULL, NULL))
== NULL)
return(-1);
return (PptpCtrlInit(PptpIncoming, PptpOutgoing));
}
/*
* PptpTShutdown()
*/
static void
PptpTShutdown(void)
{
Log(LG_PHYS2, ("PPTP: Total shutdown"));
ghash_destroy(&gPptpTuns);
}
/*
* PptpInit()
*/
static int
PptpInit(Link l)
{
PptpInfo pptp;
/* Initialize this link */
pptp = (PptpInfo) (l->info = Malloc(MB_PHYS, sizeof(*pptp)));
pptp->conf.self_addr.family = AF_INET;
pptp->conf.fqdn_peer_addr = NULL;
Enable(&pptp->conf.options, PPTP_CONF_OUTCALL);
Enable(&pptp->conf.options, PPTP_CONF_DELAYED_ACK);
Enable(&pptp->conf.options, PPTP_CONF_RESOLVE_ONCE);
return(0);
}
/*
* PptpInst()
*/
static int
PptpInst(Link l, Link lt)
{
PptpInfo pptp;
PptpInfo const pptpt = (PptpInfo) lt->info;
/* Initialize this link */
pptp = (PptpInfo) (l->info = Mdup(MB_PHYS, lt->info, sizeof(*pptp)));
if (pptpt->conf.fqdn_peer_addr != NULL)
pptp->conf.fqdn_peer_addr =
Mstrdup(MB_PHYS, pptpt->conf.fqdn_peer_addr);
pptp->listener = NULL;
return(0);
}
/*
* PptpOpen()
*/
static void
PptpOpen(Link l)
{
PptpInfo const pptp = (PptpInfo) l->info;
struct sockaddr_dl hwa;
/* Check state */
switch (l->state) {
case PHYS_STATE_DOWN:
if (PptpOriginate(l) < 0) {
Log(LG_PHYS, ("[%s] PPTP call failed", l->name));
PhysDown(l, STR_ERROR, NULL);
return;
}
l->state = PHYS_STATE_CONNECTING;
break;
case PHYS_STATE_CONNECTING:
if (pptp->originate) /* our call to peer is already in progress */
break;
if (pptp->outcall) {
/* Hook up nodes */
Log(LG_PHYS, ("[%s] PPTP: attaching to peer's outgoing call", l->name));
if (PptpHookUp(l) < 0) {
PptpDoClose(l);
/* We should not set state=DOWN as PptpResult() will be called once more */
break;
}
if (GetPeerEther(&pptp->peer_addr, &hwa)) {
if_indextoname(hwa.sdl_index, pptp->peer_iface);
memcpy(pptp->peer_mac_addr, LLADDR(&hwa), sizeof(pptp->peer_mac_addr));
};
(*pptp->cinfo.answer)(pptp->cinfo.cookie,
PPTP_OCR_RESL_OK, 0, 0, 64000 /*XXX*/ );
/* Report UP if there was no error. */
if (l->state == PHYS_STATE_CONNECTING) {
l->state = PHYS_STATE_UP;
PhysUp(l);
}
return;
}
return; /* wait for peer's incoming pptp call to complete */
case PHYS_STATE_UP:
PhysUp(l);
return;
default:
assert(0);
}
}
/*
* PptpOriginate()
*
* Initiate an "incoming" or an "outgoing" call to the remote site
*/
static int
PptpOriginate(Link l)
{
PptpInfo const pptp = (PptpInfo) l->info;
struct pptplinkinfo linfo;
const u_short port = pptp->conf.peer_port ?
pptp->conf.peer_port : PPTP_PORT;
pptp->originate = TRUE;
pptp->outcall = Enabled(&pptp->conf.options, PPTP_CONF_OUTCALL);
memset(&linfo, 0, sizeof(linfo));
linfo.cookie = l;
linfo.result = PptpResult;
linfo.setLinkInfo = PptpSetLinkInfo;
linfo.cancel = PptpCancel;
strlcpy(pptp->callingnum, pptp->conf.callingnum, sizeof(pptp->callingnum));
strlcpy(pptp->callednum, pptp->conf.callednum, sizeof(pptp->callednum));
if ((!Enabled(&pptp->conf.options, PPTP_CONF_RESOLVE_ONCE)) &&
(pptp->conf.fqdn_peer_addr != NULL)) {
struct u_range rng;
if (ParseRange(pptp->conf.fqdn_peer_addr, &rng, ALLOW_IPV4|ALLOW_IPV6))
pptp->conf.peer_addr = rng;
}
if (!pptp->outcall) {
int frameType = PPTP_FRAMECAP_SYNC;
if (l->rep && !RepIsSync(l))
frameType = PPTP_FRAMECAP_ASYNC;
PptpCtrlInCall(&pptp->cinfo, &linfo,
&pptp->conf.self_addr, &pptp->conf.peer_addr.addr, port,
PPTP_BEARCAP_ANY, frameType,
PPTP_CALL_MIN_BPS, PPTP_CALL_MAX_BPS,
pptp->callingnum, pptp->callednum, "");
} else {
PptpCtrlOutCall(&pptp->cinfo, &linfo,
&pptp->conf.self_addr, &pptp->conf.peer_addr.addr, port,
PPTP_BEARCAP_ANY, PPTP_FRAMECAP_ANY,
PPTP_CALL_MIN_BPS, PPTP_CALL_MAX_BPS,
pptp->callednum, "");
}
if (pptp->cinfo.cookie == NULL)
return(-1);
pptp->self_addr = pptp->conf.self_addr;
pptp->peer_addr = pptp->conf.peer_addr.addr;
pptp->peer_port = port;
return(0);
}
/*
* PptpClose()
*/
static void
PptpClose(Link l)
{
PptpDoClose(l);
}
/*
* PptpShutdown()
*/
static void
PptpShutdown(Link l)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (pptp->conf.fqdn_peer_addr)
Freee(pptp->conf.fqdn_peer_addr);
if (pptp->listener) {
PptpCtrlUnListen(pptp->listener);
pptp->listener = NULL;
}
PptpUnhook(l);
Freee(l->info);
}
/*
* PptpDoClose()
*/
static void
PptpDoClose(Link l)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (l->state != PHYS_STATE_DOWN) /* avoid double close */
(*pptp->cinfo.close)(pptp->cinfo.cookie, PPTP_CDN_RESL_ADMIN, 0, 0);
}
/*
* PptpUnhook()
*/
static void
PptpUnhook(Link l)
{
PptpInfo const pptp = (PptpInfo) l->info;
char path[NG_PATHSIZ];
int csock = -1;
if (pptp->tun == NULL)
return;
/* Get a temporary netgraph socket node */
if (NgMkSockNode(NULL, &csock, NULL) == -1) {
Perror("PPTP: NgMkSockNode");
return;
}
pptp->tun->refs--;
snprintf(path, sizeof(path), "[%lx]:", (u_long)pptp->tun->node_id);
if (pptp->tun->refs == 0) {
/* Disconnect session hook. */
NgFuncShutdownNode(csock, l->name, path);
ghash_remove(gPptpTuns, pptp->tun);
Freee(pptp->tun);
#ifdef NG_PPTPGRE_HOOK_SESSION_F
} else {
char hook[NG_HOOKSIZ];
snprintf(hook, sizeof(hook), NG_PPTPGRE_HOOK_SESSION_F, pptp->cid);
NgFuncDisconnect(csock, l->name, path, hook);
#endif
}
close(csock);
pptp->tun = NULL;
}
/*
* PptpOriginated()
*/
static int
PptpOriginated(Link l)
{
PptpInfo const pptp = (PptpInfo) l->info;
return(pptp->originate ? LINK_ORIGINATE_LOCAL : LINK_ORIGINATE_REMOTE);
}
/*
* PptpIsSync()
*/
static int
PptpIsSync(Link l)
{
PptpInfo const pptp = (PptpInfo) l->info;
return (pptp->sync);
}
static int
PptpSetAccm(Link l, u_int32_t xmit, u_int32_t recv)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (!pptp->cinfo.close || !pptp->cinfo.cookie)
return (-1);
(*pptp->cinfo.setLinkInfo)(pptp->cinfo.cookie, xmit, recv);
return (0);
}
static int
PptpSetCallingNum(Link l, void *buf)
{
PptpInfo const pptp = (PptpInfo) l->info;
strlcpy(pptp->conf.callingnum, buf, sizeof(pptp->conf.callingnum));
return(0);
}
static int
PptpSetCalledNum(Link l, void *buf)
{
PptpInfo const pptp = (PptpInfo) l->info;
strlcpy(pptp->conf.callednum, buf, sizeof(pptp->conf.callednum));
return(0);
}
static int
PptpSelfName(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (pptp->cinfo.cookie)
return(PptpCtrlGetSelfName(&pptp->cinfo, buf, buf_len));
((char*)buf)[0]=0;
return (0);
}
static int
PptpPeerName(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (pptp->cinfo.cookie)
return(PptpCtrlGetPeerName(&pptp->cinfo, buf, buf_len));
((char*)buf)[0]=0;
return (0);
}
static int
PptpSelfAddr(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (u_addrtoa(&pptp->self_addr, buf, buf_len))
return(0);
else
return(-1);
}
static int
PptpPeerAddr(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (u_addrtoa(&pptp->peer_addr, buf, buf_len))
return(0);
else
return(-1);
}
static int
PptpPeerPort(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (snprintf(buf, buf_len, "%d", pptp->peer_port))
return(0);
else
return(-1);
}
static int
PptpPeerMacAddr(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (pptp->peer_iface[0]) {
ether_ntoa_r((struct ether_addr *)pptp->peer_mac_addr, buf);
return (0);
}
((char*)buf)[0]=0;
return(0);
}
static int
PptpPeerIface(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
if (pptp->peer_iface[0]) {
strlcpy(buf, pptp->peer_iface, buf_len);
return (0);
}
((char*)buf)[0]=0;
return(0);
}
static int
PptpCallingNum(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
strlcpy((char*)buf, pptp->callingnum, buf_len);
return(0);
}
static int
PptpCalledNum(Link l, void *buf, size_t buf_len)
{
PptpInfo const pptp = (PptpInfo) l->info;
strlcpy((char*)buf, pptp->callednum, buf_len);
return(0);
}
/*
* PptpStat()
*/
void
PptpStat(Context ctx)
{
PptpInfo const pptp = (PptpInfo) ctx->lnk->info;
char buf[32];
Printf("PPTP configuration:\r\n");
Printf("\tSelf addr : %s",
u_addrtoa(&pptp->conf.self_addr, buf, sizeof(buf)));
if (pptp->conf.self_port)
Printf(", port %u", pptp->conf.self_port);
Printf("\r\n");
Printf("\tPeer FQDN : %s\r\n", pptp->conf.fqdn_peer_addr);
Printf("\tPeer range : %s",
u_rangetoa(&pptp->conf.peer_addr, buf, sizeof(buf)));
if (pptp->conf.peer_port)
Printf(", port %u", pptp->conf.peer_port);
Printf("\r\n");
Printf("\tCalling number: %s\r\n", pptp->conf.callingnum);
Printf("\tCalled number: %s\r\n", pptp->conf.callednum);
Printf("PPTP options:\r\n");
OptStat(ctx, &pptp->conf.options, gConfList);
Printf("PPTP status:\r\n");
if (ctx->lnk->state != PHYS_STATE_DOWN) {
Printf("\tIncoming : %s\r\n", (pptp->originate?"NO":"YES"));
Printf("\tCurrent self : %s",
u_addrtoa(&pptp->self_addr, buf, sizeof(buf)));
PptpSelfName(ctx->lnk, buf, sizeof(buf));
Printf(" (%s)\r\n", buf);
Printf("\tCurrent peer : %s, port %u",
u_addrtoa(&pptp->peer_addr, buf, sizeof(buf)), pptp->peer_port);
PptpPeerName(ctx->lnk, buf, sizeof(buf));
Printf(" (%s)\r\n", buf);
if (pptp->peer_iface[0]) {
ether_ntoa_r((struct ether_addr *)pptp->peer_mac_addr, buf);
Printf("\tCurrent peer : %s at %s\r\n", buf, pptp->peer_iface);
}
Printf("\tFraming : %s\r\n", (pptp->sync?"Sync":"Async"));
Printf("\tCalling number: %s\r\n", pptp->callingnum);
Printf("\tCalled number: %s\r\n", pptp->callednum);
}
}
/*
* PptpResult()
*
* The control code calls this function to report a PPTP link
* being connected, disconnected, or failing to connect.
*/
static void
PptpResult(void *cookie, const char *errmsg, int frameType)
{
PptpInfo pptp;
Link l;
struct sockaddr_dl hwa;
/* It this fake call? */
if (!cookie)
return;
l = (Link)cookie;
pptp = (PptpInfo) l->info;
switch (l->state) {
case PHYS_STATE_CONNECTING:
if (!errmsg) {
/* Hook up nodes */
Log(LG_PHYS, ("[%s] PPTP call successful", l->name));
if (PptpHookUp(l) < 0) {
PptpDoClose(l);
/* We should not set state=DOWN as PptpResult() will be called once more */
break;
}
if (pptp->originate && !pptp->outcall)
(*pptp->cinfo.connected)(pptp->cinfo.cookie, 64000 /*XXX*/ );
/* Report UP if there was no error. */
if (l->state == PHYS_STATE_CONNECTING) {
if (GetPeerEther(&pptp->peer_addr, &hwa)) {
if_indextoname(hwa.sdl_index, pptp->peer_iface);
memcpy(pptp->peer_mac_addr, LLADDR(&hwa), sizeof(pptp->peer_mac_addr));
};
/* OK */
l->state = PHYS_STATE_UP;
pptp->sync = (frameType&PPTP_FRAMECAP_ASYNC)?0:1;
PhysUp(l);
}
} else {
Log(LG_PHYS, ("[%s] PPTP call failed", l->name));
PptpUnhook(l); /* For the (*connected)() error. */
l->state = PHYS_STATE_DOWN;
u_addrclear(&pptp->self_addr);
u_addrclear(&pptp->peer_addr);
pptp->peer_port = 0;
pptp->callingnum[0]=0;
pptp->callednum[0]=0;
pptp->peer_iface[0] = 0;
PhysDown(l, STR_CON_FAILED, errmsg);
}
break;
case PHYS_STATE_UP:
assert(errmsg);
Log(LG_PHYS, ("[%s] PPTP call terminated", l->name));
PptpUnhook(l);
l->state = PHYS_STATE_DOWN;
u_addrclear(&pptp->self_addr);
u_addrclear(&pptp->peer_addr);
pptp->peer_port = 0;
pptp->callingnum[0]=0;
pptp->callednum[0]=0;
pptp->peer_iface[0] = 0;
PhysDown(l, STR_DROPPED, NULL);
break;
case PHYS_STATE_DOWN:
return;
default:
assert(0);
}
}
/*
* PptpSetLinkInfo()
*
* Received LinkInfo from peer;
*/
void
PptpSetLinkInfo(void *cookie, u_int32_t sa, u_int32_t ra)
{
Link l;
/* It this fake call? */
if (!cookie)
return;
l = (Link)cookie;
if (l->rep != NULL)
RepSetAccm(l, sa, ra);
}
static int
PptpTunEQ(struct ghash *g, const void *item1, const void *item2)
{
const struct pptptun *tun1 = item1;
const struct pptptun *tun2 = item2;
if (u_addrcompare(&tun1->self_addr, &tun2->self_addr) == 0 &&
u_addrcompare(&tun1->peer_addr, &tun2->peer_addr) == 0)
return (1);
return (0);
}
static u_int32_t
PptpTunHash(struct ghash *g, const void *item)
{
const struct pptptun *tun = item;
return (u_addrtoid(&tun->self_addr) + u_addrtoid(&tun->peer_addr));
}
/*
* PptpHookUp()
*
* Connect the PPTP/GRE node to the PPP node
*/
static int
PptpHookUp(Link l)
{
const PptpInfo pi = (PptpInfo)l->info;
char ksockpath[NG_PATHSIZ];
char pptppath[NG_PATHSIZ];
struct ngm_mkpeer mkp;
struct ng_pptpgre_conf gc;
struct sockaddr_storage self_addr, peer_addr;
struct u_addr u_self_addr, u_peer_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;
int csock = -1;
char path[NG_PATHSIZ];
char hook[NG_HOOKSIZ];
PptpTun tun = NULL;
/* Get session info */
memset(&gc, 0, sizeof(gc));
PptpCtrlGetSessionInfo(&pi->cinfo, &u_self_addr,
&u_peer_addr, &gc.cid, &gc.peerCid, &gc.recvWin, &gc.peerPpd);
pi->cid = gc.cid;
u_addrtosockaddr(&u_self_addr, 0, &self_addr);
u_addrtosockaddr(&u_peer_addr, 0, &peer_addr);
if (!PhysGetUpperHook(l, path, hook)) {
Log(LG_PHYS, ("[%s] PPTP: can't get upper hook", l->name));
return(-1);
}
/* Get a temporary netgraph socket node */
if (NgMkSockNode(NULL, &csock, NULL) == -1) {
Perror("PPTP: NgMkSockNode");
return(-1);
}
#ifdef NG_PPTPGRE_HOOK_SESSION_F
{
struct pptptun tmptun;
tmptun.self_addr = u_self_addr;
tmptun.peer_addr = u_peer_addr;
tun = ghash_get(gPptpTuns, &tmptun);
}
#endif
snprintf(pptppath, sizeof(pptppath), "%s.%s", path, hook);
if (tun == NULL) {
tun = (PptpTun)Malloc(MB_PHYS, sizeof(*tun));
tun->self_addr = u_self_addr;
tun->peer_addr = u_peer_addr;
if (ghash_put(gPptpTuns, tun) == -1) {
Perror("[%s] PPTP: ghash_put", l->name);
Freee(tun);
close(csock);
return(-1);
}
/* Attach PPTP/GRE node to PPP node */
strcpy(mkp.type, NG_PPTPGRE_NODE_TYPE);
strlcpy(mkp.ourhook, hook, sizeof(mkp.ourhook));
#ifdef NG_PPTPGRE_HOOK_SESSION_F
snprintf(mkp.peerhook, sizeof(mkp.peerhook), NG_PPTPGRE_HOOK_SESSION_F, pi->cid);
#else
strcpy(mkp.peerhook, NG_PPTPGRE_HOOK_UPPER);
#endif
if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
Perror("[%s] PPTP: can't attach %s node", l->name, NG_PPTPGRE_NODE_TYPE);
ghash_remove(gPptpTuns, tun);
Freee(tun);
close(csock);
return(-1);
}
/* Get pptpgre node ID */
if ((tun->node_id = NgGetNodeID(csock, pptppath)) == 0) {
Perror("[%s] Cannot get %s node id", l->name, NG_PPTPGRE_NODE_TYPE);
ghash_remove(gPptpTuns, tun);
Freee(tun);
close(csock);
return(-1);
};
tun->refs++;
pi->tun = tun;
/* Attach ksocket node to PPTP/GRE node */
strcpy(mkp.type, NG_KSOCKET_NODE_TYPE);
strcpy(mkp.ourhook, NG_PPTPGRE_HOOK_LOWER);
if (u_self_addr.family==AF_INET6) {
//ng_ksocket doesn't support inet6 name
snprintf(mkp.peerhook, sizeof(mkp.peerhook), "%d/%d/%d", PF_INET6, SOCK_RAW, IPPROTO_GRE);
} else {
snprintf(mkp.peerhook, sizeof(mkp.peerhook), "inet/raw/gre");
}
if (NgSendMsg(csock, pptppath, NGM_GENERIC_COOKIE,
NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
Perror("[%s] PPTP: can't attach %s node", l->name, NG_KSOCKET_NODE_TYPE);
close(csock);
return(-1);
}
snprintf(ksockpath, sizeof(ksockpath),
"%s.%s", pptppath, NG_PPTPGRE_HOOK_LOWER);
/* increase recvspace to avoid packet loss due to very small GRE recv buffer. */
ksso->level=SOL_SOCKET;
ksso->name=SO_RCVBUF;
((int *)(ksso->value))[0]=48*1024;
if (NgSendMsg(csock, ksockpath, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_SETOPT, &u, sizeof(u)) < 0) {
Perror("[%s] PPTP: can't setsockopt %s node",
l->name, NG_KSOCKET_NODE_TYPE);
}
/* Bind ksocket socket to local IP address */
if (NgSendMsg(csock, ksockpath, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_BIND, &self_addr, self_addr.ss_len) < 0) {
Perror("[%s] PPTP: can't bind() %s node", l->name, NG_KSOCKET_NODE_TYPE);
close(csock);
return(-1);
}
/* Connect ksocket socket to remote IP address */
if (NgSendMsg(csock, ksockpath, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_CONNECT, &peer_addr, peer_addr.ss_len) < 0 &&
errno != EINPROGRESS) { /* happens in -current (weird) */
Perror("[%s] PPTP: can't connect() %s node",
l->name, NG_KSOCKET_NODE_TYPE);
close(csock);
return(-1);
}
#ifdef NG_PPTPGRE_HOOK_SESSION_F
} else {
struct ngm_connect cn;
snprintf(cn.path, sizeof(cn.path), "[%x]:", tun->node_id);
strlcpy(cn.ourhook, hook, sizeof(mkp.ourhook));
snprintf(cn.peerhook, sizeof(mkp.peerhook), NG_PPTPGRE_HOOK_SESSION_F, pi->cid);
if (NgSendMsg(csock, path, NGM_GENERIC_COOKIE,
NGM_CONNECT, &cn, sizeof(cn)) < 0) {
Perror("[%s] PPTP: can't connect to %s node",
l->name, NG_PPTPGRE_NODE_TYPE);
close(csock);
return(-1);
}
tun->refs++;
pi->tun = tun;
#endif
}
/* Configure PPTP/GRE node */
gc.enabled = 1;
gc.enableDelayedAck = Enabled(&pi->conf.options, PPTP_CONF_DELAYED_ACK);
gc.enableAlwaysAck = Enabled(&pi->conf.options, PPTP_CONF_ALWAYS_ACK);
#if NGM_PPTPGRE_COOKIE >= 1082548365
gc.enableWindowing = Enabled(&pi->conf.options, PPTP_CONF_WINDOWING);
#endif
if (NgSendMsg(csock, pptppath, NGM_PPTPGRE_COOKIE,
NGM_PPTPGRE_SET_CONFIG, &gc, sizeof(gc)) < 0) {
Perror("[%s] PPTP: can't config %s node", l->name, NG_PPTPGRE_NODE_TYPE);
close(csock);
return(-1);
}
close(csock);
return(0);
}
/*
* PptpIncoming()
*
* The control code calls this function to report that some
* remote PPTP client has asked us if we will accept an incoming
* call relayed over PPTP.
*/
static struct pptplinkinfo
PptpIncoming(struct pptpctrlinfo *cinfo,
struct u_addr *self, struct u_addr *peer, in_port_t port, int bearType,
const char *callingNum,
const char *calledNum,
const char *subAddress)
{
return(PptpPeerCall(cinfo, self, peer, port, TRUE, callingNum, calledNum, subAddress));
}
/*
* PptpOutgoing()
*
* The control code calls this function to report that some
* remote PPTP client has asked us if we will dial out to some
* phone number. We don't actually do this, but some clients
* initiate their connections as outgoing calls for some reason.
*/
static struct pptplinkinfo
PptpOutgoing(struct pptpctrlinfo *cinfo,
struct u_addr *self, struct u_addr *peer, in_port_t port, int bearType,
int frameType, int minBps, int maxBps,
const char *calledNum, const char *subAddress)
{
return(PptpPeerCall(cinfo, self, peer, port, FALSE, "", calledNum, subAddress));
}
/*
* PptpPeerCall()
*
* Peer has initiated a call (either incoming or outgoing; either
* way it's the same to us). If we have an available link that may
* accept calls from the peer's IP addresss and port, then say yes.
*/
static struct pptplinkinfo
PptpPeerCall(struct pptpctrlinfo *cinfo,
struct u_addr *self, struct u_addr *peer, in_port_t port, int incoming,
const char *callingNum,
const char *calledNum,
const char *subAddress)
{
struct pptplinkinfo linfo;
Link l = NULL;
PptpInfo pi = NULL;
int k;
memset(&linfo, 0, sizeof(linfo));
linfo.cookie = NULL;
linfo.result = PptpResult;
linfo.setLinkInfo = PptpSetLinkInfo;
linfo.cancel = PptpCancel;
if (gShutdownInProgress) {
Log(LG_PHYS, ("Shutdown sequence in progress, ignoring request."));
return(linfo);
}
if (OVERLOAD()) {
Log(LG_PHYS, ("Daemon overloaded, ignoring request."));
return(linfo);
}
/* Find a suitable link; prefer the link best matching peer's IP address */
for (k = 0; k < gNumLinks; k++) {
Link l2;
PptpInfo pi2;
if (!gLinks[k] || gLinks[k]->type != &gPptpPhysType)
continue;
l2 = gLinks[k];
pi2 = (PptpInfo)l2->info;
/* See if link is feasible */
if ((!PhysIsBusy(l2)) &&
Enabled(&l2->conf.options, LINK_CONF_INCOMING) &&
(u_addrempty(&pi2->conf.self_addr) || (u_addrcompare(&pi2->conf.self_addr, self) == 0)) &&
IpAddrInRange(&pi2->conf.peer_addr, peer) &&
(!pi2->conf.peer_port || pi2->conf.peer_port == port)) {
/* Link is feasible; now see if it's preferable */
if (!pi || pi2->conf.peer_addr.width > pi->conf.peer_addr.width) {
l = l2;
pi = pi2;
if (u_rangehost(&pi->conf.peer_addr)) {
break; /* Nothing could be better */
}
}
}
}
if (l != NULL && l->tmpl)
l = LinkInst(l, NULL, 0, 0);
/* If no link is suitable, can't take the call */
if (l == NULL) {
Log(LG_PHYS, ("No free PPTP link with requested parameters "
"was found"));
return(linfo);
}
pi = (PptpInfo)l->info;
Log(LG_PHYS, ("[%s] Accepting PPTP connection", l->name));
/* Got one */
linfo.cookie = l;
l->state = PHYS_STATE_CONNECTING;
pi->cinfo = *cinfo;
pi->originate = FALSE;
pi->outcall = !incoming;
pi->sync = 1;
pi->self_addr = *self;
pi->peer_addr = *peer;
pi->peer_port = port;
strlcpy(pi->callingnum, callingNum, sizeof(pi->callingnum));
strlcpy(pi->callednum, calledNum, sizeof(pi->callednum));
PhysIncoming(l);
return(linfo);
}
/*
* PptpCancel()
*
* The control code calls this function to cancel a
* local outgoing call in progress.
*/
static void
PptpCancel(void *cookie)
{
PptpInfo pi;
Link l;
/* It this fake call? */
if (!cookie)
return;
l = (Link)cookie;
pi = (PptpInfo) l->info;
Log(LG_PHYS, ("[%s] PPTP call cancelled in state %s",
l->name, gPhysStateNames[l->state]));
if (l->state == PHYS_STATE_DOWN)
return;
l->state = PHYS_STATE_DOWN;
u_addrclear(&pi->peer_addr);
pi->peer_port = 0;
pi->callingnum[0]=0;
pi->callednum[0]=0;
pi->peer_iface[0] = 0;
PhysDown(l, STR_CON_FAILED0, NULL);
}
/*
* PptpListenUpdate()
*/
static void
PptpListenUpdate(Link l)
{
PptpInfo pi = (PptpInfo) l->info;
if (pi->listener == NULL) {
if (Enabled(&l->conf.options, LINK_CONF_INCOMING)) {
/* Set up listening for incoming connections */
if ((pi->listener =
PptpCtrlListen(&pi->conf.self_addr, pi->conf.self_port))
== NULL) {
Log(LG_ERR, ("PPTP: Error, can't listen for connection!"));
}
}
} else {
if (!Enabled(&l->conf.options, LINK_CONF_INCOMING)) {
PptpCtrlUnListen(pi->listener);
pi->listener = NULL;
}
}
}
/*
* PptpSetCommand()
*/
static int
PptpSetCommand(Context ctx, int ac, char *av[], void *arg)
{
PptpInfo const pi = (PptpInfo) ctx->lnk->info;
char **fqdn_peer_addr = &pi->conf.fqdn_peer_addr;
struct u_range rng;
int port;
switch ((intptr_t)arg) {
case SET_SELFADDR:
case SET_PEERADDR:
if ((ac == 1 || ac == 2) && (intptr_t)arg == SET_PEERADDR) {
if (*fqdn_peer_addr)
Freee(*fqdn_peer_addr);
*fqdn_peer_addr = Mstrdup(MB_PHYS, av[0]);
}
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_CALLINGNUM:
if (ac != 1)
return(-1);
strlcpy(pi->conf.callingnum, av[0], sizeof(pi->conf.callingnum));
break;
case SET_CALLEDNUM:
if (ac != 1)
return(-1);
strlcpy(pi->conf.callednum, av[0], sizeof(pi->conf.callednum));
break;
case SET_ENABLE:
EnableCommand(ac, av, &pi->conf.options, gConfList);
PptpListenUpdate(ctx->lnk);
break;
case SET_DISABLE:
DisableCommand(ac, av, &pi->conf.options, gConfList);
PptpListenUpdate(ctx->lnk);
break;
default:
assert(0);
}
return(0);
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>