/*
* Copyright (c) 2001-2002 Packet Design, LLC.
* All rights reserved.
*
* Subject to the following obligations and disclaimer of warranty,
* use and redistribution of this software, in source or object code
* forms, with or without modifications are expressly permitted by
* Packet Design; provided, however, that:
*
* (i) Any and all reproductions of the source or object code
* must include the copyright notice above and the following
* disclaimer of warranties; and
* (ii) No rights are granted, in any manner or form, to use
* Packet Design trademarks, including the mark "PACKET DESIGN"
* on advertising, endorsements, or otherwise except as such
* appears in the above copyright notice or in the software.
*
* THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
* TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
* REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
* THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
* OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
* OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
* OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
* RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
* LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
* OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
* DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
* USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 PACKET DESIGN IS ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Archie Cobbs <archie@freebsd.org>
*/
#include "ppp/ppp_defs.h"
#include "ppp/ppp_log.h"
#include "ppp/ppp_engine.h"
#include "ppp/ppp_fsm_option.h"
#include "ppp/ppp_auth.h"
#include "ppp/ppp_lcp.h"
#include "ppp/ppp_link.h"
#include "ppp/ppp_channel.h"
#include "ppp/ppp_l2tp_avp.h"
#include "ppp/ppp_l2tp_server.h"
#include "ppp/ppp_l2tp_ctrl.h"
#include <net/ethernet.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netgraph/ng_ksocket.h>
#define L2TP_MTYPE "ppp_l2tp"
#define L2TP_OUTPUT_MTYPE "ppp_l2tp.output"
#define L2TP_DEVICE_MTYPE "ppp_l2tp.device"
#define L2TP_PEER_MTYPE "ppp_l2tp.peer"
/* Win2k is too stupid to handle changing the UDP port */
#define L2TP_CHANGE_PORT 0
#if 0 /* win2k seems to require at least 1500 */
#define L2TP_MRU \
(ETHER_MAX_LEN /* standard ethernet frame */ \
- ETHER_CRC_LEN /* ethernet crc */ \
- ETHER_HDR_LEN /* ethernet header */ \
- sizeof(struct ip) /* ip header */ \
- sizeof(struct udp) /* udp header */ \
- 10 /* l2tp header */
- 2) /* ppp protocol field */
#else
#define L2TP_MRU LCP_DEFAULT_MRU
#endif
#define L2TP_MRRU LCP_DEFAULT_MRRU
/* L2TP server info */
struct ppp_l2tp_server {
struct ppp_l2tp_server_info info; /* client info */
struct ppp_log *log; /* ppp log */
struct ppp_engine *engine; /* ppp engine */
struct ghash *peers; /* active peers */
struct pevent_ctx *ev_ctx; /* event context */
pthread_mutex_t *mutex; /* mutex */
struct in_addr ip; /* server ip address */
u_int16_t port; /* server udp port */
int sock; /* udp listen socket */
struct pevent *sock_event; /* event context */
};
/* We keep one of these for each control connection */
struct ppp_l2tp_peer {
struct ppp_l2tp_server *s; /* pointer to server */
struct ppp_l2tp_ctrl *ctrl; /* ctrl connection */
void *carg; /* client callbck arg */
struct ppp_l2tp_sess *sess; /* l2tp session */
struct ppp_channel *chan; /* pointer to channel */
struct ppp_auth_config auth; /* auth config */
char node[32]; /* node path */
char hook[NG_HOOKSIZ]; /* node hook */
char logname[32]; /* peer logname */
struct in_addr ip; /* peer ip address */
u_int16_t port; /* peer port */
u_char closed; /* closed by client */
};
/* L2TP control callbacks */
static ppp_l2tp_ctrl_terminated_t ppp_l2tp_server_ctrl_terminated;
static ppp_l2tp_initiated_t ppp_l2tp_server_initiated;
static ppp_l2tp_connected_t ppp_l2tp_server_connected;
static ppp_l2tp_terminated_t ppp_l2tp_server_terminated;
static const struct ppp_l2tp_ctrl_cb ppp_l2tp_server_ctrl_cb = {
ppp_l2tp_server_ctrl_terminated,
ppp_l2tp_server_initiated,
ppp_l2tp_server_connected,
ppp_l2tp_server_terminated,
NULL,
NULL,
};
/* Device methods */
static ppp_channel_open_t ppp_l2tp_server_device_open;
static ppp_channel_close_t ppp_l2tp_server_device_close;
static ppp_channel_destroy_t ppp_l2tp_server_device_destroy;
static ppp_channel_free_output_t ppp_l2tp_server_device_free_output;
static ppp_channel_set_link_info_t ppp_l2tp_server_device_set_link_info;
static ppp_channel_get_origination_t ppp_l2tp_server_device_get_origination;
static ppp_channel_get_node_t ppp_l2tp_server_device_get_node;
static ppp_channel_get_hook_t ppp_l2tp_server_device_get_hook;
static ppp_channel_is_async_t ppp_l2tp_server_device_is_async;
static ppp_channel_get_mtu_t ppp_l2tp_server_device_get_mtu;
static ppp_channel_get_acfcomp_t ppp_l2tp_server_device_get_acfcomp;
static ppp_channel_get_pfcomp_t ppp_l2tp_server_device_get_pfcomp;
static struct ppp_channel_meth ppp_l2tp_server_device_meth = {
ppp_l2tp_server_device_open,
ppp_l2tp_server_device_close,
ppp_l2tp_server_device_destroy,
ppp_l2tp_server_device_free_output,
ppp_l2tp_server_device_set_link_info,
ppp_l2tp_server_device_get_origination,
ppp_l2tp_server_device_get_node,
ppp_l2tp_server_device_get_hook,
ppp_l2tp_server_device_is_async,
ppp_l2tp_server_device_get_mtu,
ppp_l2tp_server_device_get_acfcomp,
ppp_l2tp_server_device_get_pfcomp,
};
/* Other internal functions */
static int ppp_l2tp_server_new_sess(struct ppp_l2tp_peer *peer,
struct ppp_l2tp_sess *sess);
static void ppp_l2tp_server_destroy(struct ppp_l2tp_server **sp);
static void ppp_l2tp_server_peer_destroy(struct ppp_l2tp_peer **peerp);
static void ppp_l2tp_server_device_output(struct ppp_l2tp_peer *peer,
enum ppp_channeloutput type, ...);
static void ppp_l2tp_server_close_client(struct ppp_l2tp_peer *peer);
static pevent_handler_t ppp_l2tp_server_sock_event;
static const int one = 1;
/* Macro for logging */
#define LOG(sev, fmt, args...) PPP_LOG(s->log, sev, fmt , ## args)
/***********************************************************************
PUBLIC FUNCTIONS
***********************************************************************/
/*
* Start the L2TP server associated with a ppp engine.
*/
int
ppp_l2tp_server_start(struct ppp_engine *engine,
const struct ppp_l2tp_server_info *info,
struct in_addr ip, u_int16_t port, u_int max_conn)
{
struct ppp_log *const elog = ppp_engine_get_log(engine);
struct sockaddr_in sin;
struct ppp_l2tp_server *s;
/* Sanity */
if (engine == NULL || info->arg == NULL) {
errno = EINVAL;
return (-1);
}
/* See if server already exists */
if ((s = ppp_engine_get_l2tp_server(engine)) != NULL) {
errno = EALREADY;
return (-1);
}
/* Create new server */
if ((s = MALLOC(L2TP_MTYPE, sizeof(*s))) == NULL)
return (-1);
memset(s, 0, sizeof(*s));
s->engine = engine;
s->ev_ctx = ppp_engine_get_ev_ctx(engine);
s->mutex = ppp_engine_get_mutex(engine);
s->sock = -1;
s->log = ppp_log_dup(elog);
s->info = *info;
s->ip = ip;
s->port = (port != 0) ? port : L2TP_PORT;
/* Create control connection hash table */
if ((s->peers = ghash_create(s, 0, 0,
L2TP_MTYPE, NULL, NULL, NULL, NULL)) == NULL) {
ppp_log_put(elog, LOG_ERR, "ghash_create: %m");
goto fail;
}
/* Setup UDP socket that listens for new connections */
if ((s->sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
ppp_log_put(elog, LOG_ERR, "socket: %m");
goto fail;
}
if (setsockopt(s->sock, SOL_SOCKET,
SO_REUSEADDR, &one, sizeof(one)) == -1) {
ppp_log_put(elog, LOG_ERR, "setsockopt: %m");
goto fail;
}
if (setsockopt(s->sock, SOL_SOCKET,
SO_REUSEPORT, &one, sizeof(one)) == -1) {
ppp_log_put(elog, LOG_ERR, "setsockopt: %m");
goto fail;
}
memset(&sin, 0, sizeof(sin));
#ifndef __linux__
sin.sin_len = sizeof(sin);
#endif
sin.sin_family = AF_INET;
sin.sin_addr = ip;
sin.sin_port = htons(s->port);
if (bind(s->sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
ppp_log_put(elog, LOG_ERR, "bind: %m");
goto fail;
}
if (pevent_register(s->ev_ctx, &s->sock_event, PEVENT_RECURRING,
s->mutex, ppp_l2tp_server_sock_event, s, PEVENT_READ,
s->sock) == -1) {
ppp_log_put(elog, LOG_ERR, "pevent_register: %m");
goto fail;
}
/* Done */
ppp_engine_set_l2tp_server(engine, s);
return (0);
fail:
/* Clean up after failure */
pevent_unregister(&s->sock_event);
if (s->sock != -1)
close(s->sock);
ghash_destroy(&s->peers);
FREE(L2TP_MTYPE, s);
return (-1);
}
/*
* Stop the L2TP server associated with a ppp engine.
*
* We can't completely destroy it, because there may be L2TP devices
* in use by ppp_link's that still exist. The ppp_link's are responsible
* for destroying their devices, not us.
*/
void
ppp_l2tp_server_stop(struct ppp_engine *engine)
{
struct ppp_l2tp_server *s;
/* Get and clear L2TP server */
if ((s = ppp_engine_get_l2tp_server(engine)) == NULL)
return;
ppp_engine_set_l2tp_server(s->engine, NULL);
/* Stop accepting new connections */
pevent_unregister(&s->sock_event);
(void)close(s->sock);
s->sock = -1;
/* If there are no control connections, clean up now */
if (ghash_size(s->peers) == 0) {
ppp_l2tp_server_destroy(&s);
return;
}
/* Destroy all control connections */
while (1) {
struct ppp_l2tp_peer *peer;
struct ghash_walk walk;
ghash_walk_init(s->peers, &walk);
while ((peer = ghash_walk_next(s->peers, &walk)) != NULL) {
/*
* Destroy control connection and session (if any).
* We do a 'close' before the 'destroy' to cause at
* least one StopCCN packet to be generated, in an
* attempt to be a little nicer to the peer.
*/
if (peer->ctrl != NULL) {
ppp_l2tp_ctrl_shutdown(peer->ctrl,
L2TP_RESULT_SHUTDOWN, 0, NULL);
ppp_l2tp_ctrl_destroy(&peer->ctrl);
peer->sess = NULL;
}
/* Notify client side peer is gone */
ppp_l2tp_server_close_client(peer);
/* Destroy peer if it has no more references */
if (peer->chan == NULL) {
ppp_l2tp_server_peer_destroy(&peer);
break; /* restart; walk is invalid */
}
}
if (peer == NULL)
break;
}
}
/*
* Destroy the L2TP server object.
*/
static void
ppp_l2tp_server_destroy(struct ppp_l2tp_server **sp)
{
struct ppp_l2tp_server *const s = *sp;
/* Sanity */
if (s == NULL)
return;
*sp = NULL;
/* Deallocate */
assert(ghash_size(s->peers) == 0);
pevent_unregister(&s->sock_event);
(void)close(s->sock);
ghash_destroy(&s->peers);
ppp_log_close(&s->log);
FREE(L2TP_MTYPE, s);
}
/*
* Close a L2TP connection.
*/
void
ppp_l2tp_server_close(struct ppp_engine *engine, struct ppp_l2tp_peer **peerp)
{
struct ppp_l2tp_peer *const peer = *peerp;
if (peer == NULL)
return;
*peerp = NULL;
peer->carg = NULL; /* don't call client 'destroy' method */
if (peer->chan != NULL)
ppp_l2tp_server_device_close(peer->chan);
}
/*
* Get the client handle for the L2TP channel associated with a device.
*/
void *
ppp_l2tp_server_get_client_info(struct ppp_channel *chan)
{
struct ppp_l2tp_peer *const peer = chan->priv;
if (chan->meth != &ppp_l2tp_server_device_meth) {
errno = EINVAL;
return (NULL);
}
if (peer->carg == NULL)
errno = ENXIO;
return (peer->carg);
}
/***********************************************************************
L2TP CONTROL CALLBACKS
***********************************************************************/
/*
* This is called when a control connection is terminated for any reason
* other than a call ppp_l2tp_ctrl_destroy().
*/
static void
ppp_l2tp_server_ctrl_terminated(struct ppp_l2tp_ctrl *ctrl,
u_int16_t result, u_int16_t error, const char *errmsg)
{
struct ppp_l2tp_peer *peer = ppp_l2tp_ctrl_get_cookie(ctrl);
struct ppp_l2tp_server *const s = peer->s;
/* Debugging */
LOG(LOG_DEBUG, "%s: invoked ctrl=%p errmsg=\"%s\"",
__FUNCTION__, ctrl, errmsg);
/* Notify client side peer is gone */
ppp_l2tp_server_close_client(peer);
/* There should be no session */
assert(peer->sess == NULL);
peer->ctrl = NULL;
/* Destroy peer if it has no more references */
if (peer->chan == NULL)
ppp_l2tp_server_peer_destroy(&peer);
}
/*
* This callback is used to report the peer's initiating a new incoming
* or outgoing call.
*/
static void
ppp_l2tp_server_initiated(struct ppp_l2tp_ctrl *ctrl,
struct ppp_l2tp_sess *sess, int out,
const struct ppp_l2tp_avp_list *avps)
{
struct ppp_l2tp_peer *const peer = ppp_l2tp_ctrl_get_cookie(ctrl);
struct ppp_l2tp_server *const s = peer->s;
/* Debugging */
LOG(LOG_DEBUG, "%s: invoked ctrl=%p sess=%p out=%d",
__FUNCTION__, ctrl, sess, out);
/* If call is incoming, wait for peer to reply */
if (!out) {
ppp_l2tp_sess_set_cookie(sess, peer);
return;
}
/* Accept call */
if (ppp_l2tp_server_new_sess(peer, sess) == -1) {
ppp_l2tp_terminate(sess, L2TP_RESULT_ERROR,
L2TP_ERROR_GENERIC, strerror(errno));
return;
}
/* Notify control code */
ppp_l2tp_connected(sess, NULL);
}
/*
* This callback is used to report successful connection of a remotely
* initiated incoming call (see ppp_l2tp_initiated_t) or a locally initiated
* outgoing call (see ppp_l2tp_initiate()).
*/
static void
ppp_l2tp_server_connected(struct ppp_l2tp_sess *sess,
const struct ppp_l2tp_avp_list *avps)
{
struct ppp_l2tp_peer *const peer = ppp_l2tp_sess_get_cookie(sess);
struct ppp_l2tp_server *const s = peer->s;
/* Debugging */
LOG(LOG_DEBUG, "%s: invoked sess=%p", __FUNCTION__, sess);
/* Accept call */
if (ppp_l2tp_server_new_sess(peer, sess) == -1) {
ppp_l2tp_terminate(sess, L2TP_RESULT_ERROR,
L2TP_ERROR_GENERIC, strerror(errno));
return;
}
}
/*
* This callback is called when any call, whether successfully connected
* or not, is terminated for any reason other than explict termination
* from the link side (via a call to either ppp_l2tp_terminate() or
* ppp_l2tp_ctrl_destroy()).
*/
static void
ppp_l2tp_server_terminated(struct ppp_l2tp_sess *sess,
u_int16_t result, u_int16_t error, const char *errmsg)
{
struct ppp_l2tp_peer *const peer = ppp_l2tp_sess_get_cookie(sess);
struct ppp_l2tp_server *const s = peer->s;
char buf[128];
/* Debugging */
LOG(LOG_DEBUG, "%s: invoked sess=%p", __FUNCTION__, sess);
/* Sanity */
assert(peer->sess == NULL || peer->sess == sess);
assert(peer->ctrl != NULL);
/* Control side is notifying us session is down */
peer->sess = NULL;
snprintf(buf, sizeof(buf), "result=%u error=%u errmsg=\"%s\"",
result, error, (errmsg != NULL) ? errmsg : "");
LOG(LOG_INFO, "call from %s terminated: %s", peer->logname, buf);
/* Notify client side peer is gone */
ppp_l2tp_server_close_client(peer);
/* Notify PPP stack session is down */
if (peer->chan != NULL) {
ppp_l2tp_server_device_output(peer,
PPP_CHANNEL_OUTPUT_DOWN_FATAL,
(errmsg != NULL) ? errmsg : "administratively closed");
}
}
/***********************************************************************
INTERNAL FUNCTIONS
***********************************************************************/
/*
* Read an incoming packet that might be a new L2TP connection.
*/
static void
ppp_l2tp_server_sock_event(void *arg)
{
struct ppp_l2tp_server *const s = arg;
struct ppp_l2tp_avp_list *avps = NULL;
struct ppp_l2tp_peer *peer = NULL;
#if !L2TP_CHANGE_PORT
union {
u_char buf[sizeof(struct ng_ksocket_sockopt) + sizeof(int)];
struct ng_ksocket_sockopt sockopt;
} sockopt_buf;
struct ng_ksocket_sockopt *const sockopt = &sockopt_buf.sockopt;
#endif
struct ppp_log *log = NULL;
struct ngm_connect connect;
struct ngm_rmhook rmhook;
struct ngm_mkpeer mkpeer;
struct sockaddr_in peer_sin;
struct sockaddr_in sin;
const size_t bufsize = 8192;
u_int16_t *buf = NULL;
char hook[NG_HOOKSIZ];
socklen_t sin_len;
char namebuf[64];
ng_ID_t node_id;
int csock = -1;
int dsock = -1;
int len;
/* Allocate buffer */
if ((buf = MALLOC(TYPED_MEM_TEMP, bufsize)) == NULL) {
LOG(LOG_ERR, "malloc: %m");
goto fail;
}
/* Read packet */
sin_len = sizeof(peer_sin);
if ((len = recvfrom(s->sock, buf, bufsize, 0,
(struct sockaddr *)&peer_sin, &sin_len)) == -1) {
LOG(LOG_ERR, "recvfrom: %m");
goto fail;
}
/* Drop it if it's not an initial L2TP packet */
if (len < 12)
goto fail;
if ((ntohs(buf[0]) & 0xcb0f) != 0xc802 || ntohs(buf[1]) < 12
|| buf[2] != 0 || buf[3] != 0 || buf[4] != 0 || buf[5] != 0)
goto fail;
/* Create a new peer */
if ((peer = MALLOC(L2TP_PEER_MTYPE, sizeof(*peer))) == NULL) {
LOG(LOG_ERR, "malloc: %m");
return;
}
memset(peer, 0, sizeof(*peer));
peer->s = s;
peer->ip = peer_sin.sin_addr;
peer->port = ntohs(peer_sin.sin_port);
/* Check with client library */
snprintf(peer->logname, sizeof(peer->logname), "%s:%u",
inet_ntoa(peer_sin.sin_addr), ntohs(peer_sin.sin_port));
if ((peer->carg = (*s->info.admit)(s->info.arg, peer,
peer->ip, peer->port, &peer->auth, peer->logname,
sizeof(peer->logname))) == NULL)
goto fail;
/* Create vendor name AVP */
if (s->info.vendor != NULL) {
if ((avps = ppp_l2tp_avp_list_create()) == NULL) {
LOG(LOG_ERR, "%s: %m", "ppp_l2tp_avp_list_create");
goto fail;
}
if (ppp_l2tp_avp_list_append(avps, 1, 0, AVP_VENDOR_NAME,
s->info.vendor, strlen(s->info.vendor)) == -1) {
LOG(LOG_ERR, "%s: %m", "ppp_l2tp_avp_list_append");
goto fail;
}
}
/* Create a log for the control connection */
if ((log = ppp_log_prefix(s->log, "%s: ", peer->logname)) == NULL) {
LOG(LOG_ERR, "%s: %m", "ppp_log_prefix");
goto fail;
}
/* Create a new control connection */
if ((peer->ctrl = ppp_l2tp_ctrl_create(s->ev_ctx, s->mutex,
&ppp_l2tp_server_ctrl_cb, log, 0, ntohl(peer_sin.sin_addr.s_addr),
&node_id, hook, avps, NULL, 0)) == NULL) {
LOG(LOG_ERR, "%s: %m", "ppp_l2tp_ctrl_create");
goto fail;
}
ppp_l2tp_ctrl_set_cookie(peer->ctrl, peer);
log = NULL; /* log is consumed by control connection */
/* Get a temporary netgraph socket node */
if (NgMkSockNode(NULL, &csock, &dsock) == -1) {
LOG(LOG_ERR, "%s: %m", "NgMkSockNode");
goto fail;
}
/* Connect to l2tp netgraph node "lower" hook */
snprintf(namebuf, sizeof(namebuf), "[%lx]:", (u_long)node_id);
memset(&connect, 0, sizeof(connect));
strlcpy(connect.path, namebuf, sizeof(connect.path));
strlcpy(connect.ourhook, hook, sizeof(connect.ourhook));
strlcpy(connect.peerhook, hook, sizeof(connect.peerhook));
if (NgSendMsg(csock, ".", NGM_GENERIC_COOKIE,
NGM_CONNECT, &connect, sizeof(connect)) == -1) {
LOG(LOG_ERR, "%s: %m", "connect");
goto fail;
}
/* Write the received packet to the node */
if (NgSendData(dsock, hook, (u_char *)buf, len) == -1) {
LOG(LOG_ERR, "%s: %m", "NgSendData");
goto fail;
}
/* Disconnect from netgraph node "lower" hook */
memset(&rmhook, 0, sizeof(rmhook));
strlcpy(rmhook.ourhook, hook, sizeof(rmhook.ourhook));
if (NgSendMsg(csock, ".", NGM_GENERIC_COOKIE,
NGM_RMHOOK, &rmhook, sizeof(rmhook)) == -1) {
LOG(LOG_ERR, "%s: %m", "rmhook");
goto fail;
}
/* Attach a new UDP socket to "lower" hook */
memset(&mkpeer, 0, sizeof(mkpeer));
strlcpy(mkpeer.type, NG_KSOCKET_NODE_TYPE, sizeof(mkpeer.type));
strlcpy(mkpeer.ourhook, hook, sizeof(mkpeer.ourhook));
strlcpy(mkpeer.peerhook, "inet/dgram/udp", sizeof(mkpeer.peerhook));
if (NgSendMsg(csock, namebuf, NGM_GENERIC_COOKIE,
NGM_MKPEER, &mkpeer, sizeof(mkpeer)) == -1) {
LOG(LOG_ERR, "%s: %m", "mkpeer");
goto fail;
}
/* Point name at ksocket node */
strlcat(namebuf, hook, sizeof(namebuf));
#if !L2TP_CHANGE_PORT
/* Make UDP port reusable */
memset(&sockopt_buf, 0, sizeof(sockopt_buf));
sockopt->level = SOL_SOCKET;
sockopt->name = SO_REUSEADDR;
memcpy(sockopt->value, &one, sizeof(int));
if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_SETOPT, sockopt, sizeof(sockopt_buf)) == -1) {
LOG(LOG_ERR, "%s: %m", "setsockopt");
goto fail;
}
sockopt->name = SO_REUSEPORT;
if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_SETOPT, sockopt, sizeof(sockopt_buf)) == -1) {
LOG(LOG_ERR, "%s: %m", "setsockopt");
goto fail;
}
#endif
/* Bind socket to a new port */
memset(&sin, 0, sizeof(sin));
#ifndef __linux__
sin.sin_len = sizeof(sin);
#endif
sin.sin_family = AF_INET;
sin.sin_addr = s->ip;
#if L2TP_CHANGE_PORT
sin.sin_port = 0; /* "choose any free port" */
#else
sin.sin_port = htons(s->port);
#endif
if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_BIND, &sin, sizeof(sin)) == -1) {
LOG(LOG_ERR, "%s: %m", "bind");
goto fail;
}
#if L2TP_CHANGE_PORT
/* Set up reverse NAT mapping for the new port */
if (s->info.natmap != NULL) {
struct ng_mesg *const reply = (struct ng_mesg *)buf;
struct sockaddr_in *const self_sin
= (struct sockaddr_in *)reply->data;
/* Get kernel-assigned UDP port number */
if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_GETNAME, NULL, 0) == -1) {
LOG(LOG_ERR, "%s: %m", "getsockname");
goto fail;
}
if (NgRecvMsg(csock, reply, bufsize, NULL) == -1) {
LOG(LOG_ERR, "%s: %m", "recvmsg");
goto fail;
}
if ((*s->info.natmap)(s->info.arg, self_sin->sin_addr,
ntohs(self_sin->sin_port), s->port, peer_sin.sin_addr,
ntohs(peer_sin.sin_port)) == -1) {
LOG(LOG_ERR, "%s: %m", "can't reverse NAT map");
goto fail;
}
}
#endif
/* Connect socket to remote peer's IP and port */
if (NgSendMsg(csock, namebuf, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_CONNECT, &peer_sin, sizeof(peer_sin)) == -1
&& errno != EINPROGRESS) {
LOG(LOG_ERR, "%s: %m", "connect");
goto fail;
}
/* Add peer to our hash table */
if (ghash_put(s->peers, peer) == -1) {
LOG(LOG_ERR, "%s: %m", "ghash_put");
goto fail;
}
/* Clean up and return */
ppp_l2tp_avp_list_destroy(&avps);
(void)close(csock);
(void)close(dsock);
FREE(TYPED_MEM_TEMP, buf);
return;
fail:
/* Clean up after failure */
if (csock != -1)
(void)close(csock);
if (dsock != -1)
(void)close(dsock);
if (peer != NULL) {
ppp_l2tp_server_close_client(peer);
ppp_l2tp_ctrl_destroy(&peer->ctrl);
FREE(L2TP_PEER_MTYPE, peer);
}
ppp_l2tp_avp_list_destroy(&avps);
ppp_log_close(&log);
FREE(TYPED_MEM_TEMP, buf);
}
/*
* Create a new L2TP peer object corresponding to a L2TP channel
* and start up a new PPP link/bundle.
*/
static int
ppp_l2tp_server_new_sess(struct ppp_l2tp_peer *peer, struct ppp_l2tp_sess *sess)
{
struct ppp_l2tp_server *const s = peer->s;
struct ppp_link_config link_config;
struct ppp_log *log = NULL;
const char *hook;
ng_ID_t node_id;
int esave;
/* We allow only one session per control connection */
if (peer->sess != NULL) {
errno = EALREADY;
return (-1);
}
/* Get this link's node and hook */
ppp_l2tp_sess_get_hook(sess, &node_id, &hook);
snprintf(peer->node, sizeof(peer->node), "[%lx]:", (u_long)node_id);
strlcpy(peer->hook, hook, sizeof(peer->hook));
/* Create a new PPP device for this session */
if ((peer->chan = MALLOC(L2TP_DEVICE_MTYPE,
sizeof(*peer->chan))) == NULL) {
LOG(LOG_ERR, "can't allocate new channel: %m");
goto fail;
}
memset(peer->chan, 0, sizeof(*peer->chan));
peer->chan->meth = &ppp_l2tp_server_device_meth;
peer->chan->priv = peer;
/* Create device output message port */
if ((peer->chan->outport
= mesg_port_create("ppp_l2tp_server")) == NULL) {
LOG(LOG_ERR, "can't create mesg_port: %m");
goto fail;
}
/* Create log for the new PPP link by prefixing the engine's log */
if ((log = ppp_engine_get_log(s->engine)) != NULL
&& (log = ppp_log_prefix(log, "%s: ", peer->logname)) == NULL) {
LOG(LOG_ERR, "can't create link log: %m");
goto fail;
}
/* Configure the new PPP link */
memset(&link_config, 0, sizeof(link_config));
link_config.auth = peer->auth;
link_config.max_self_mru = L2TP_MRU;
link_config.max_self_mrru = L2TP_MRRU;
link_config.multilink = 1;
link_config.eid.class = PPP_EID_CLASS_IP;
link_config.eid.length = sizeof(s->ip);
memcpy(link_config.eid.value, &s->ip, sizeof(s->ip));
/* Add new link to the PPP engine */
if (ppp_link_create(s->engine, peer->chan, &link_config, log) == -1) {
LOG(LOG_ERR, "can't create link: %m");
goto fail;
}
log = NULL;
/* Notify PPP engine link is up */
ppp_l2tp_server_device_output(peer, PPP_CHANNEL_OUTPUT_UP);
/* Done */
peer->sess = sess;
return (0);
fail:
/* Clean up after failure */
esave = errno;
ppp_log_close(&log);
if (peer->chan != NULL) {
if (peer->chan->outport != NULL)
mesg_port_destroy(&peer->chan->outport);
FREE(L2TP_DEVICE_MTYPE, peer->chan);
}
errno = esave;
return (-1);
}
/*
* Output indication from the device.
*/
static void
ppp_l2tp_server_device_output(struct ppp_l2tp_peer *peer,
enum ppp_channeloutput type, ...)
{
struct ppp_l2tp_server *const s = peer->s;
struct ppp_channel_output *output;
/* Get output object */
if ((output = MALLOC(L2TP_OUTPUT_MTYPE, sizeof(*output))) == NULL) {
LOG(LOG_ERR, "can't create l2tp output: %m");
return;
}
memset(output, 0, sizeof(*output));
output->type = type;
/* Get extra args */
switch (output->type) {
case PPP_CHANNEL_OUTPUT_DOWN_FATAL:
case PPP_CHANNEL_OUTPUT_DOWN_NONFATAL:
{
const char *msg;
va_list args;
/* Get string message */
va_start(args, type);
msg = va_arg(args, const char *);
va_end(args);
if ((output->info = STRDUP(L2TP_OUTPUT_MTYPE, msg)) == NULL) {
LOG(LOG_ERR, "can't create l2tp output: %m");
FREE(L2TP_OUTPUT_MTYPE, output);
return;
}
break;
}
case PPP_CHANNEL_OUTPUT_UP:
break;
}
/* Send message */
if (mesg_port_put(peer->chan->outport, output) == -1) {
LOG(LOG_ERR, "can't send l2tp output: %m");
ppp_l2tp_server_device_free_output(peer->chan, output);
return;
}
}
/*
* Notify client code that connection is gone.
* Make sure that we only do this once however.
*/
static void
ppp_l2tp_server_close_client(struct ppp_l2tp_peer *peer)
{
struct ppp_l2tp_server *const s = peer->s;
void *const peer_carg = peer->carg;
if (peer_carg == NULL)
return;
peer->carg = NULL;
(*s->info.destroy)(s->info.arg, peer_carg);
}
/*
* Destroy a peer.
*/
static void
ppp_l2tp_server_peer_destroy(struct ppp_l2tp_peer **peerp)
{
struct ppp_l2tp_peer *peer = *peerp;
struct ppp_l2tp_server *s;
/* Sanity checks */
if (peer == NULL)
return;
*peerp = NULL;
assert(peer->ctrl == NULL);
assert(peer->sess == NULL);
assert(peer->chan == NULL);
/* Destroy peer */
s = peer->s;
ghash_remove(s->peers, peer);
FREE(L2TP_PEER_MTYPE, peer);
/* Destroy server if shutting down and no more peers left */
if (s->sock == -1 && ghash_size(s->peers) == 0)
ppp_l2tp_server_destroy(&s);
}
/***********************************************************************
L2TP DEVICE METHODS
***********************************************************************/
static void
ppp_l2tp_server_device_open(struct ppp_channel *chan)
{
return;
}
static void
ppp_l2tp_server_device_close(struct ppp_channel *chan)
{
struct ppp_l2tp_peer *const peer = chan->priv;
struct ppp_l2tp_server *const s = peer->s;
/* Logging */
if (!peer->closed) {
LOG(LOG_INFO, "closing L2TP connection with %s", peer->logname);
peer->closed = 1;
}
/* Terminate the L2TP channel */
if (peer->sess != NULL) {
ppp_l2tp_terminate(peer->sess, L2TP_RESULT_ADMIN, 0, NULL);
peer->sess = NULL;
}
/* Notify upper layers link is down */
ppp_l2tp_server_device_output(peer,
PPP_CHANNEL_OUTPUT_DOWN_FATAL, "administratively closed");
}
static void
ppp_l2tp_server_device_destroy(struct ppp_channel **chanp)
{
struct ppp_channel *const chan = *chanp;
struct ppp_channel_output *output;
struct ppp_l2tp_peer *peer;
struct ppp_l2tp_server *s;
/* Sanity */
if (chan == NULL)
return;
*chanp = NULL;
peer = chan->priv;
assert(peer->chan == chan);
s = peer->s;
/* Close client code's side of the device */
ppp_l2tp_server_close_client(peer);
/* Terminate the L2TP channel */
if (peer->sess != NULL) {
ppp_l2tp_terminate(peer->sess, L2TP_RESULT_ADMIN, 0, NULL);
peer->sess = NULL;
}
/* Destroy the device object */
while ((output = mesg_port_get(chan->outport, 0)) != NULL)
ppp_l2tp_server_device_free_output(chan, output);
mesg_port_destroy(&chan->outport);
FREE(L2TP_DEVICE_MTYPE, chan);
peer->chan = NULL;
/* Destroy peer if it has no more references */
if (peer->ctrl == NULL)
ppp_l2tp_server_peer_destroy(&peer);
}
static void
ppp_l2tp_server_device_free_output(struct ppp_channel *chan,
struct ppp_channel_output *output)
{
FREE(L2TP_OUTPUT_MTYPE, output->info);
FREE(L2TP_OUTPUT_MTYPE, output);
}
static void
ppp_l2tp_server_device_set_link_info(struct ppp_channel *chan, u_int32_t accm)
{
/* XXX implement me? */
}
static int
ppp_l2tp_server_device_get_origination(struct ppp_channel *chan)
{
return (PPP_PEER); /* we don't initiate any calls ourself */
}
static const char *
ppp_l2tp_server_device_get_node(struct ppp_channel *chan)
{
struct ppp_l2tp_peer *const peer = chan->priv;
return (peer->node);
}
static const char *
ppp_l2tp_server_device_get_hook(struct ppp_channel *chan)
{
struct ppp_l2tp_peer *const peer = chan->priv;
return (peer->hook);
}
static int
ppp_l2tp_server_device_is_async(struct ppp_channel *chan)
{
return (0);
}
static u_int
ppp_l2tp_server_device_get_mtu(struct ppp_channel *chan)
{
return (L2TP_MRU);
}
static int
ppp_l2tp_server_device_get_acfcomp(struct ppp_channel *chan)
{
return (1);
}
static int
ppp_l2tp_server_device_get_pfcomp(struct ppp_channel *chan)
{
return (1);
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>