src/sys/net/pipex.c

3071 lines
74 KiB
C

/* $OpenBSD: pipex.c,v 1.153 2024/01/23 17:57:21 mvs Exp $ */
/*-
* Copyright (c) 2009 Internet Initiative Japan Inc.
* 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.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/conf.h>
#include <sys/time.h>
#include <sys/timeout.h>
#include <sys/kernel.h>
#include <sys/pool.h>
#include <sys/percpu.h>
#include <sys/mutex.h>
#include <net/if.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net/if_dl.h>
#include <net/radix.h>
#include <net/route.h>
#include <net/ppp_defs.h>
#include <net/ppp-comp.h>
#include <net/netisr.h>
#include "pf.h"
#if NPF > 0
#include <net/pfvar.h>
#endif
#include "bpfilter.h"
#if NBPFILTER > 0
#include <net/bpf.h>
#endif
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#ifdef INET6
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#endif
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <crypto/arc4.h>
#include <net/pipex.h>
#include "pipex_local.h"
struct mutex pipex_list_mtx = MUTEX_INITIALIZER(IPL_SOFTNET);
struct pool pipex_session_pool;
struct pool mppe_key_pool;
/*
* Global data
* Locks used to protect global data
* A atomic operation
* I immutable after creation
* L pipex_list_mtx
*/
int pipex_enable = 0; /* [A] */
struct pipex_hash_head
pipex_session_list, /* [L] master session list */
pipex_close_wait_list, /* [L] expired session list */
pipex_peer_addr_hashtable[PIPEX_HASH_SIZE], /* [L] peer's address hash */
pipex_id_hashtable[PIPEX_HASH_SIZE]; /* [L] peer id hash */
struct radix_node_head *pipex_rd_head4 = NULL; /* [L] */
struct timeout pipex_timer_ch; /* callout timer context */
int pipex_prune = 1; /* [I] walk list every seconds */
struct mbuf_queue pipexoutq = MBUF_QUEUE_INITIALIZER(
IFQ_MAXLEN, IPL_SOFTNET);
/* borrow an mbuf pkthdr field */
#define ph_ppp_proto ether_vtag
#ifdef PIPEX_DEBUG
int pipex_debug = 0; /* [A] systcl net.inet.ip.pipex_debug */
#endif
/* PPP compression == MPPE is assumed, so don't answer CCP Reset-Request. */
#define PIPEX_NO_CCP_RESETACK 1
/************************************************************************
* Core functions
************************************************************************/
void
pipex_init(void)
{
int i;
static int pipex_init_done = 0;
if (pipex_init_done++)
return;
rn_init(sizeof(struct sockaddr_in6));
pool_init(&pipex_session_pool, sizeof(struct pipex_session), 0,
IPL_SOFTNET, PR_WAITOK, "ppxss", NULL);
pool_init(&mppe_key_pool, PIPEX_MPPE_KEYLEN * PIPEX_MPPE_NOLDKEY, 0,
IPL_SOFTNET, PR_WAITOK, "mppekey", NULL);
LIST_INIT(&pipex_session_list);
LIST_INIT(&pipex_close_wait_list);
for (i = 0; i < nitems(pipex_id_hashtable); i++)
LIST_INIT(&pipex_id_hashtable[i]);
for (i = 0; i < nitems(pipex_peer_addr_hashtable); i++)
LIST_INIT(&pipex_peer_addr_hashtable[i]);
}
void
pipex_destroy_all_sessions(void *ownersc)
{
struct pipex_session *session, *session_tmp;
mtx_enter(&pipex_list_mtx);
LIST_FOREACH_SAFE(session, &pipex_session_list, session_list,
session_tmp) {
if (session->flags & PIPEX_SFLAGS_ITERATOR)
continue;
if (session->ownersc == ownersc) {
KASSERT((session->flags & PIPEX_SFLAGS_PPPX) == 0);
pipex_unlink_session_locked(session);
pipex_rele_session(session);
}
}
mtx_leave(&pipex_list_mtx);
}
int
pipex_ioctl(void *ownersc, u_long cmd, caddr_t data)
{
int ret = 0;
switch (cmd) {
case PIPEXGSTAT:
ret = pipex_get_stat((struct pipex_session_stat_req *)data,
ownersc);
break;
case PIPEXGCLOSED:
ret = pipex_get_closed((struct pipex_session_list_req *)data,
ownersc);
break;
default:
ret = ENOTTY;
break;
}
return (ret);
}
/************************************************************************
* Software Interrupt Handler
************************************************************************/
void
pipexintr(void)
{
struct mbuf_list ml;
struct mbuf *m;
struct pipex_session *session;
NET_ASSERT_LOCKED();
mq_delist(&pipexoutq, &ml);
while ((m = ml_dequeue(&ml)) != NULL) {
struct ifnet *ifp;
session = m->m_pkthdr.ph_cookie;
ifp = if_get(session->proto.pppoe.over_ifidx);
if (ifp != NULL) {
struct pipex_pppoe_header *pppoe;
int len;
pppoe = mtod(m, struct pipex_pppoe_header *);
len = ntohs(pppoe->length);
ifp->if_output(ifp, m, &session->peer.sa, NULL);
counters_pkt(session->stat_counters, pxc_opackets,
pxc_obytes, len);
} else {
m_freem(m);
counters_inc(session->stat_counters, pxc_oerrors);
}
if_put(ifp);
pipex_rele_session(session);
}
}
/************************************************************************
* Session management functions
************************************************************************/
int
pipex_init_session(struct pipex_session **rsession,
struct pipex_session_req *req)
{
struct pipex_session *session;
#ifdef PIPEX_PPPOE
struct ifnet *over_ifp = NULL;
#endif
/* Checks requested parameters. */
switch (req->pr_protocol) {
#ifdef PIPEX_PPPOE
case PIPEX_PROTO_PPPOE:
if (req->pr_peer_address.ss_family != AF_UNSPEC)
return (EINVAL);
break;
#endif
#if defined(PIPEX_L2TP) || defined(PIPEX_PPTP)
case PIPEX_PROTO_PPTP:
case PIPEX_PROTO_L2TP:
switch (req->pr_peer_address.ss_family) {
case AF_INET:
if (req->pr_peer_address.ss_len !=
sizeof(struct sockaddr_in))
return (EINVAL);
break;
#ifdef INET6
case AF_INET6:
if (req->pr_peer_address.ss_len !=
sizeof(struct sockaddr_in6))
return (EINVAL);
break;
#endif
default:
return (EPROTONOSUPPORT);
}
if (req->pr_peer_address.ss_family !=
req->pr_local_address.ss_family ||
req->pr_peer_address.ss_len !=
req->pr_local_address.ss_len)
return (EINVAL);
break;
#endif /* defined(PIPEX_PPTP) || defined(PIPEX_L2TP) */
default:
return (EPROTONOSUPPORT);
}
#ifdef PIPEX_MPPE
if ((req->pr_ppp_flags & PIPEX_PPP_MPPE_ACCEPTED) != 0) {
switch (req->pr_mppe_recv.keylenbits) {
case 40:
case 56:
case 128:
break;
default:
return (EINVAL);
}
}
if ((req->pr_ppp_flags & PIPEX_PPP_MPPE_ENABLED) != 0) {
switch (req->pr_mppe_send.keylenbits) {
case 40:
case 56:
case 128:
break;
default:
return (EINVAL);
}
}
if ((req->pr_ppp_flags & PIPEX_PPP_MPPE_REQUIRED) != 0) {
if ((req->pr_ppp_flags &
(PIPEX_PPP_MPPE_ACCEPTED | PIPEX_PPP_MPPE_ENABLED)) !=
(PIPEX_PPP_MPPE_ACCEPTED | PIPEX_PPP_MPPE_ENABLED))
return (EINVAL);
}
#endif
#ifdef PIPEX_PPPOE
if (req->pr_protocol == PIPEX_PROTO_PPPOE) {
over_ifp = if_unit(req->pr_proto.pppoe.over_ifname);
if (over_ifp == NULL)
return (EINVAL);
}
#endif
/* prepare a new session */
session = pool_get(&pipex_session_pool, PR_WAITOK | PR_ZERO);
refcnt_init(&session->pxs_refcnt);
mtx_init(&session->pxs_mtx, IPL_SOFTNET);
session->state = PIPEX_STATE_INITIAL;
session->protocol = req->pr_protocol;
session->session_id = req->pr_session_id;
session->peer_session_id = req->pr_peer_session_id;
session->peer_mru = req->pr_peer_mru;
session->timeout_sec = req->pr_timeout_sec;
session->ppp_flags = req->pr_ppp_flags;
session->ppp_id = req->pr_ppp_id;
session->stat_counters = counters_alloc(pxc_ncounters);
session->ip_address.sin_family = AF_INET;
session->ip_address.sin_len = sizeof(struct sockaddr_in);
session->ip_address.sin_addr = req->pr_ip_address;
session->ip_netmask.sin_family = AF_INET;
session->ip_netmask.sin_len = sizeof(struct sockaddr_in);
session->ip_netmask.sin_addr = req->pr_ip_netmask;
if (session->ip_netmask.sin_addr.s_addr == 0L)
session->ip_netmask.sin_addr.s_addr = 0xffffffffL;
session->ip_address.sin_addr.s_addr &=
session->ip_netmask.sin_addr.s_addr;
if (req->pr_peer_address.ss_len > 0)
memcpy(&session->peer, &req->pr_peer_address,
MIN(req->pr_peer_address.ss_len, sizeof(session->peer)));
if (req->pr_local_address.ss_len > 0)
memcpy(&session->local, &req->pr_local_address,
MIN(req->pr_local_address.ss_len, sizeof(session->local)));
#ifdef PIPEX_PPPOE
if (req->pr_protocol == PIPEX_PROTO_PPPOE) {
session->proto.pppoe.over_ifidx = over_ifp->if_index;
if_put(over_ifp);
}
#endif
#ifdef PIPEX_PPTP
if (req->pr_protocol == PIPEX_PROTO_PPTP) {
struct pipex_pptp_session *sess_pptp = &session->proto.pptp;
sess_pptp->snd_gap = 0;
sess_pptp->rcv_gap = 0;
sess_pptp->snd_una = req->pr_proto.pptp.snd_una;
sess_pptp->snd_nxt = req->pr_proto.pptp.snd_nxt;
sess_pptp->rcv_nxt = req->pr_proto.pptp.rcv_nxt;
sess_pptp->rcv_acked = req->pr_proto.pptp.rcv_acked;
sess_pptp->winsz = req->pr_proto.pptp.winsz;
sess_pptp->maxwinsz = req->pr_proto.pptp.maxwinsz;
sess_pptp->peer_maxwinsz = req->pr_proto.pptp.peer_maxwinsz;
/* last ack number */
sess_pptp->ul_snd_una = sess_pptp->snd_una - 1;
}
#endif
#ifdef PIPEX_L2TP
if (req->pr_protocol == PIPEX_PROTO_L2TP) {
struct pipex_l2tp_session *sess_l2tp = &session->proto.l2tp;
/* session keys */
sess_l2tp->tunnel_id = req->pr_proto.l2tp.tunnel_id;
sess_l2tp->peer_tunnel_id = req->pr_proto.l2tp.peer_tunnel_id;
/* protocol options */
sess_l2tp->option_flags = req->pr_proto.l2tp.option_flags;
/* initial state of dynamic context */
sess_l2tp->ns_gap = sess_l2tp->nr_gap = 0;
sess_l2tp->ns_nxt = req->pr_proto.l2tp.ns_nxt;
sess_l2tp->nr_nxt = req->pr_proto.l2tp.nr_nxt;
sess_l2tp->ns_una = req->pr_proto.l2tp.ns_una;
sess_l2tp->nr_acked = req->pr_proto.l2tp.nr_acked;
/* last ack number */
sess_l2tp->ul_ns_una = sess_l2tp->ns_una - 1;
sess_l2tp->ipsecflowinfo = req->pr_proto.l2tp.ipsecflowinfo;
}
#endif
#ifdef PIPEX_MPPE
if ((req->pr_ppp_flags & PIPEX_PPP_MPPE_ACCEPTED) != 0) {
pipex_session_init_mppe_recv(session,
req->pr_mppe_recv.stateless, req->pr_mppe_recv.keylenbits,
req->pr_mppe_recv.master_key);
}
if ((req->pr_ppp_flags & PIPEX_PPP_MPPE_ENABLED) != 0) {
pipex_session_init_mppe_send(session,
req->pr_mppe_send.stateless, req->pr_mppe_send.keylenbits,
req->pr_mppe_send.master_key);
}
#endif
*rsession = session;
return 0;
}
void
pipex_rele_session(struct pipex_session *session)
{
if (refcnt_rele(&session->pxs_refcnt) == 0)
return;
if (session->mppe_recv.old_session_keys)
pool_put(&mppe_key_pool, session->mppe_recv.old_session_keys);
counters_free(session->stat_counters, pxc_ncounters);
pool_put(&pipex_session_pool, session);
}
int
pipex_link_session(struct pipex_session *session, struct ifnet *ifp,
void *ownersc)
{
struct pipex_hash_head *chain;
struct radix_node *rn;
int error = 0;
mtx_enter(&pipex_list_mtx);
if (pipex_rd_head4 == NULL) {
if (!rn_inithead((void **)&pipex_rd_head4,
offsetof(struct sockaddr_in, sin_addr)))
panic("rn_inithead() failed on pipex_link_session()");
}
if (pipex_lookup_by_session_id_locked(session->protocol,
session->session_id)) {
error = EEXIST;
goto out;
}
session->ownersc = ownersc;
session->ifindex = ifp->if_index;
if (ifp->if_flags & IFF_POINTOPOINT)
session->flags |= PIPEX_SFLAGS_PPPX;
if ((session->flags & PIPEX_SFLAGS_PPPX) == 0 &&
!in_nullhost(session->ip_address.sin_addr)) {
if (pipex_lookup_by_ip_address_locked(
session->ip_address.sin_addr) != NULL) {
error = EADDRINUSE;
goto out;
}
rn = rn_addroute(&session->ip_address, &session->ip_netmask,
pipex_rd_head4, session->ps4_rn, RTP_STATIC);
if (rn == NULL) {
error = ENOMEM;
goto out;
}
}
LIST_INSERT_HEAD(&pipex_session_list, session, session_list);
chain = PIPEX_ID_HASHTABLE(session->session_id);
LIST_INSERT_HEAD(chain, session, id_chain);
#if defined(PIPEX_PPTP) || defined(PIPEX_L2TP)
switch (session->protocol) {
case PIPEX_PROTO_PPTP:
case PIPEX_PROTO_L2TP:
chain = PIPEX_PEER_ADDR_HASHTABLE(
pipex_sockaddr_hash_key(&session->peer.sa));
LIST_INSERT_HEAD(chain, session, peer_addr_chain);
}
#endif
/* if first session is added, start timer */
if (LIST_NEXT(session, session_list) == NULL)
pipex_timer_start();
session->state = PIPEX_STATE_OPENED;
out:
mtx_leave(&pipex_list_mtx);
return error;
}
void
pipex_unlink_session_locked(struct pipex_session *session)
{
struct radix_node *rn;
MUTEX_ASSERT_LOCKED(&pipex_list_mtx);
session->ifindex = 0;
if (session->state == PIPEX_STATE_CLOSED)
return;
if ((session->flags & PIPEX_SFLAGS_PPPX) == 0 &&
!in_nullhost(session->ip_address.sin_addr)) {
KASSERT(pipex_rd_head4 != NULL);
rn = rn_delete(&session->ip_address, &session->ip_netmask,
pipex_rd_head4, (struct radix_node *)session);
KASSERT(rn != NULL);
}
LIST_REMOVE(session, id_chain);
#if defined(PIPEX_PPTP) || defined(PIPEX_L2TP)
switch (session->protocol) {
case PIPEX_PROTO_PPTP:
case PIPEX_PROTO_L2TP:
LIST_REMOVE(session, peer_addr_chain);
break;
}
#endif
if (session->state == PIPEX_STATE_CLOSE_WAIT)
LIST_REMOVE(session, state_list);
LIST_REMOVE(session, session_list);
session->state = PIPEX_STATE_CLOSED;
/* if final session is destroyed, stop timer */
if (LIST_EMPTY(&pipex_session_list))
pipex_timer_stop();
}
void
pipex_unlink_session(struct pipex_session *session)
{
mtx_enter(&pipex_list_mtx);
pipex_unlink_session_locked(session);
mtx_leave(&pipex_list_mtx);
}
int
pipex_notify_close_session(struct pipex_session *session)
{
MUTEX_ASSERT_LOCKED(&pipex_list_mtx);
session->state = PIPEX_STATE_CLOSE_WAIT;
session->idle_time = 0;
LIST_INSERT_HEAD(&pipex_close_wait_list, session, state_list);
return (0);
}
void
pipex_export_session_stats(struct pipex_session *session,
struct pipex_statistics *stats)
{
uint64_t counters[pxc_ncounters];
memset(stats, 0, sizeof(*stats));
counters_read(session->stat_counters, counters, pxc_ncounters, NULL);
stats->ipackets = counters[pxc_ipackets];
stats->ierrors = counters[pxc_ierrors];
stats->ibytes = counters[pxc_ibytes];
stats->opackets = counters[pxc_opackets];
stats->oerrors = counters[pxc_oerrors];
stats->obytes = counters[pxc_obytes];
stats->idle_time = session->idle_time;
}
int
pipex_get_stat(struct pipex_session_stat_req *req, void *ownersc)
{
struct pipex_session *session;
int error = 0;
session = pipex_lookup_by_session_id(req->psr_protocol,
req->psr_session_id);
if (session == NULL)
return (EINVAL);
if (session->ownersc == ownersc)
pipex_export_session_stats(session, &req->psr_stat);
else
error = EINVAL;
pipex_rele_session(session);
return error;
}
int
pipex_get_closed(struct pipex_session_list_req *req, void *ownersc)
{
struct pipex_session *session, *session_tmp;
bzero(req, sizeof(*req));
mtx_enter(&pipex_list_mtx);
LIST_FOREACH_SAFE(session, &pipex_close_wait_list, state_list,
session_tmp) {
if (session->flags & PIPEX_SFLAGS_ITERATOR)
continue;
if (session->ownersc != ownersc)
continue;
req->plr_ppp_id[req->plr_ppp_id_count++] = session->ppp_id;
LIST_REMOVE(session, state_list);
session->state = PIPEX_STATE_CLOSE_WAIT2;
if (req->plr_ppp_id_count >= PIPEX_MAX_LISTREQ) {
if (!LIST_EMPTY(&pipex_close_wait_list))
req->plr_flags |= PIPEX_LISTREQ_MORE;
break;
}
}
mtx_leave(&pipex_list_mtx);
return (0);
}
struct pipex_session *
pipex_lookup_by_ip_address_locked(struct in_addr addr)
{
struct pipex_session *session;
struct sockaddr_in pipex_in4, pipex_in4mask;
MUTEX_ASSERT_LOCKED(&pipex_list_mtx);
if (pipex_rd_head4 == NULL)
return (NULL);
bzero(&pipex_in4, sizeof(pipex_in4));
pipex_in4.sin_addr = addr;
pipex_in4.sin_family = AF_INET;
pipex_in4.sin_len = sizeof(pipex_in4);
bzero(&pipex_in4mask, sizeof(pipex_in4mask));
pipex_in4mask.sin_addr.s_addr = htonl(0xFFFFFFFFL);
pipex_in4mask.sin_family = AF_INET;
pipex_in4mask.sin_len = sizeof(pipex_in4mask);
session = (struct pipex_session *)rn_lookup(&pipex_in4, &pipex_in4mask,
pipex_rd_head4);
#ifdef PIPEX_DEBUG
if (session == NULL) {
char buf[INET_ADDRSTRLEN];
PIPEX_DBG((NULL, LOG_DEBUG, "<%s> session not found (addr=%s)",
__func__, inet_ntop(AF_INET, &addr, buf, sizeof(buf))));
}
#endif
return (session);
}
struct pipex_session *
pipex_lookup_by_ip_address(struct in_addr addr)
{
struct pipex_session *session;
mtx_enter(&pipex_list_mtx);
session = pipex_lookup_by_ip_address_locked(addr);
if (session != NULL)
refcnt_take(&session->pxs_refcnt);
mtx_leave(&pipex_list_mtx);
return (session);
}
struct pipex_session *
pipex_lookup_by_session_id_locked(int protocol, int session_id)
{
struct pipex_hash_head *list;
struct pipex_session *session;
MUTEX_ASSERT_LOCKED(&pipex_list_mtx);
list = PIPEX_ID_HASHTABLE(session_id);
LIST_FOREACH(session, list, id_chain) {
if (session->protocol == protocol &&
session->session_id == session_id)
break;
}
#ifdef PIPEX_DEBUG
if (session == NULL)
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> session not found (session_id=%d)", __func__,
session_id));
#endif
return (session);
}
struct pipex_session *
pipex_lookup_by_session_id(int protocol, int session_id)
{
struct pipex_session *session;
mtx_enter(&pipex_list_mtx);
session = pipex_lookup_by_session_id_locked(protocol, session_id);
if (session != NULL)
refcnt_take(&session->pxs_refcnt);
mtx_leave(&pipex_list_mtx);
return (session);
}
/***********************************************************************
* Timer functions
***********************************************************************/
void
pipex_timer_start(void)
{
timeout_set_flags(&pipex_timer_ch, pipex_timer, NULL,
KCLOCK_NONE, TIMEOUT_PROC | TIMEOUT_MPSAFE);
timeout_add_sec(&pipex_timer_ch, pipex_prune);
}
void
pipex_timer_stop(void)
{
timeout_del(&pipex_timer_ch);
}
void
pipex_timer(void *ignored_arg)
{
struct pipex_session *session, *session_tmp;
mtx_enter(&pipex_list_mtx);
/* walk through */
LIST_FOREACH_SAFE(session, &pipex_session_list, session_list,
session_tmp) {
if (session->flags & PIPEX_SFLAGS_ITERATOR)
continue;
switch (session->state) {
case PIPEX_STATE_OPENED:
if (session->timeout_sec == 0)
continue;
session->idle_time++;
if (session->idle_time < session->timeout_sec)
continue;
pipex_notify_close_session(session);
break;
case PIPEX_STATE_CLOSE_WAIT:
case PIPEX_STATE_CLOSE_WAIT2:
/* Waiting PIPEXDSESSION from userland */
session->idle_time++;
if (session->idle_time < PIPEX_CLOSE_TIMEOUT)
continue;
/* Release the sessions when timeout */
pipex_unlink_session_locked(session);
KASSERTMSG((session->flags & PIPEX_SFLAGS_PPPX) == 0,
"FIXME session must not be released when pppx");
pipex_rele_session(session);
break;
default:
break;
}
}
if (LIST_FIRST(&pipex_session_list))
timeout_add_sec(&pipex_timer_ch, pipex_prune);
mtx_leave(&pipex_list_mtx);
}
/***********************************************************************
* Common network I/O functions. (tunnel protocol independent)
***********************************************************************/
struct pipex_session *
pipex_iterator(struct pipex_session *session,
struct pipex_session_iterator *iter, void *ownersc)
{
struct pipex_session *session_tmp;
mtx_enter(&pipex_list_mtx);
if (session)
session_tmp = LIST_NEXT(session, session_list);
else
session_tmp = LIST_FIRST(&pipex_session_list);
while (session_tmp) {
if (session_tmp->flags & PIPEX_SFLAGS_ITERATOR)
goto next;
if (session_tmp->ownersc != ownersc)
goto next;
break;
next:
session_tmp = LIST_NEXT(session_tmp, session_list);
}
if (session)
LIST_REMOVE(iter, session_list);
if (session_tmp) {
LIST_INSERT_AFTER(session_tmp,
(struct pipex_session *)&iter, session_list);
refcnt_take(&session_tmp->pxs_refcnt);
}
mtx_leave(&pipex_list_mtx);
if (session)
pipex_rele_session(session);
return (session_tmp);
}
void
pipex_ip_output(struct mbuf *m0, struct pipex_session *session)
{
int is_idle;
if ((session->flags & PIPEX_SFLAGS_MULTICAST) == 0) {
/*
* Multicast packet is a idle packet and it's not TCP.
*/
/* reset idle timer */
if (session->timeout_sec != 0) {
is_idle = 0;
m0 = ip_is_idle_packet(m0, &is_idle);
if (m0 == NULL)
goto dropped;
if (is_idle == 0) {
mtx_enter(&pipex_list_mtx);
/* update expire time */
if (session->state == PIPEX_STATE_OPENED)
session->idle_time = 0;
mtx_leave(&pipex_list_mtx);
}
}
/* adjust tcpmss */
if ((session->ppp_flags & PIPEX_PPP_ADJUST_TCPMSS) != 0) {
m0 = adjust_tcp_mss(m0, session->peer_mru);
if (m0 == NULL)
goto dropped;
}
pipex_ppp_output(m0, session, PPP_IP);
} else {
struct pipex_session_iterator iter = {
.flags = PIPEX_SFLAGS_ITERATOR,
};
struct pipex_session *session_tmp;
struct mbuf *m;
m0->m_flags &= ~(M_BCAST|M_MCAST);
session_tmp = pipex_iterator(NULL, &iter, session->ownersc);
while (session_tmp) {
m = m_copym(m0, 0, M_COPYALL, M_NOWAIT);
if (m != NULL)
pipex_ppp_output(m, session_tmp, PPP_IP);
else
counters_inc(session_tmp->stat_counters,
pxc_oerrors);
session_tmp = pipex_iterator(session_tmp,
&iter, session->ownersc);
}
m_freem(m0);
}
return;
dropped:
counters_inc(session->stat_counters, pxc_oerrors);
}
void
pipex_ppp_output(struct mbuf *m0, struct pipex_session *session, int proto)
{
u_char *cp, hdr[16];
#ifdef PIPEX_MPPE
if (pipex_session_is_mppe_enabled(session)) {
if (proto == PPP_IP) {
m0 = pipex_mppe_output(m0, session, PPP_IP);
if (m0 == NULL)
goto drop;
proto = PPP_COMP;
}
}
#endif /* PIPEX_MPPE */
cp = hdr;
if (session->protocol != PIPEX_PROTO_PPPOE) {
/* PPPoE has not address and control field */
PUTCHAR(PPP_ALLSTATIONS, cp);
PUTCHAR(PPP_UI, cp);
}
PUTSHORT(proto, cp);
M_PREPEND(m0, cp - hdr, M_NOWAIT);
if (m0 == NULL)
goto drop;
memcpy(mtod(m0, u_char *), hdr, cp - hdr);
switch (session->protocol) {
#ifdef PIPEX_PPPOE
case PIPEX_PROTO_PPPOE:
pipex_pppoe_output(m0, session);
break;
#endif
#ifdef PIPEX_PPTP
case PIPEX_PROTO_PPTP:
mtx_enter(&session->pxs_mtx);
pipex_pptp_output(m0, session, 1, 1);
mtx_leave(&session->pxs_mtx);
break;
#endif
#ifdef PIPEX_L2TP
case PIPEX_PROTO_L2TP:
pipex_l2tp_output(m0, session);
break;
#endif
default:
goto drop;
}
return;
drop:
m_freem(m0);
counters_inc(session->stat_counters, pxc_oerrors);
}
void
pipex_ppp_input(struct mbuf *m0, struct pipex_session *session, int decrypted)
{
int proto, hlen = 0;
struct mbuf *n;
#ifdef PIPEX_MPPE
again:
#endif
KASSERT(m0->m_pkthdr.len >= PIPEX_PPPMINLEN);
proto = pipex_ppp_proto(m0, session, 0, &hlen);
#ifdef PIPEX_MPPE
if (proto == PPP_COMP) {
if (decrypted)
goto drop;
/* checked this on ppp_common_input() already. */
KASSERT(pipex_session_is_mppe_accepted(session));
m_adj(m0, hlen);
m0 = pipex_mppe_input(m0, session);
if (m0 == NULL)
goto drop;
decrypted = 1;
goto again;
}
if (proto == PPP_CCP) {
if (decrypted)
goto drop;
#if NBPFILTER > 0
{
struct ifnet *ifp;
if ((ifp = if_get(session->ifindex)) != NULL) {
if (ifp->if_bpf && ifp->if_type == IFT_PPP)
bpf_mtap(ifp->if_bpf, m0, BPF_DIRECTION_IN);
}
if_put(ifp);
}
#endif
m_adj(m0, hlen);
pipex_ccp_input(m0, session);
return;
}
#endif
m_adj(m0, hlen);
if (!ALIGNED_POINTER(mtod(m0, caddr_t), uint32_t)) {
n = m_dup_pkt(m0, 0, M_NOWAIT);
if (n == NULL)
goto drop;
m_freem(m0);
m0 = n;
}
switch (proto) {
case PPP_IP:
if (!decrypted && pipex_session_is_mppe_required(session))
/*
* if ip packet received when mppe
* is required, discard it.
*/
goto drop;
pipex_ip_input(m0, session);
return;
#ifdef INET6
case PPP_IPV6:
if (!decrypted && pipex_session_is_mppe_required(session))
/*
* if ip packet received when mppe
* is required, discard it.
*/
goto drop;
pipex_ip6_input(m0, session);
return;
#endif
default:
if (decrypted)
goto drop;
/* protocol must be checked on pipex_common_input() already */
KASSERT(0);
goto drop;
}
return;
drop:
m_freem(m0);
counters_inc(session->stat_counters, pxc_ierrors);
}
void
pipex_ip_input(struct mbuf *m0, struct pipex_session *session)
{
struct ifnet *ifp;
struct ip *ip;
int len;
int is_idle;
/* change recvif */
m0->m_pkthdr.ph_ifidx = session->ifindex;
if (ISSET(session->ppp_flags, PIPEX_PPP_INGRESS_FILTER)) {
PIPEX_PULLUP(m0, sizeof(struct ip));
if (m0 == NULL)
goto drop;
/* ingress filter */
ip = mtod(m0, struct ip *);
if ((ip->ip_src.s_addr & session->ip_netmask.sin_addr.s_addr) !=
session->ip_address.sin_addr.s_addr) {
char src[INET_ADDRSTRLEN];
pipex_session_log(session, LOG_DEBUG,
"ip packet discarded by ingress filter (src %s)",
inet_ntop(AF_INET, &ip->ip_src, src, sizeof(src)));
goto drop;
}
}
/* idle timer */
if (session->timeout_sec != 0) {
is_idle = 0;
m0 = ip_is_idle_packet(m0, &is_idle);
if (m0 == NULL)
goto drop;
if (is_idle == 0) {
/* update expire time */
mtx_enter(&pipex_list_mtx);
if (session->state == PIPEX_STATE_OPENED)
session->idle_time = 0;
mtx_leave(&pipex_list_mtx);
}
}
/* adjust tcpmss */
if (session->ppp_flags & PIPEX_PPP_ADJUST_TCPMSS) {
m0 = adjust_tcp_mss(m0, session->peer_mru);
if (m0 == NULL)
goto drop;
}
#if NPF > 0
pf_pkt_addr_changed(m0);
#endif
len = m0->m_pkthdr.len;
if ((ifp = if_get(session->ifindex)) == NULL)
goto drop;
#if NBPFILTER > 0
if (ifp->if_bpf)
bpf_mtap_af(ifp->if_bpf, AF_INET, m0, BPF_DIRECTION_IN);
#endif
counters_pkt(ifp->if_counters, ifc_ipackets, ifc_ibytes, len);
counters_pkt(session->stat_counters, pxc_ipackets, pxc_ibytes, len);
ipv4_input(ifp, m0);
if_put(ifp);
return;
drop:
m_freem(m0);
counters_inc(session->stat_counters, pxc_ierrors);
}
#ifdef INET6
void
pipex_ip6_input(struct mbuf *m0, struct pipex_session *session)
{
struct ifnet *ifp;
int len;
/* change recvif */
m0->m_pkthdr.ph_ifidx = session->ifindex;
/*
* XXX: what is reasonable ingress filter ???
* only one address is enough ??
*/
/* XXX: we must define idle packet for IPv6(ICMPv6). */
/*
* XXX: tcpmss adjustment for IPv6 is required???
* We may use PMTUD in IPv6....
*/
#if NPF > 0
pf_pkt_addr_changed(m0);
#endif
len = m0->m_pkthdr.len;
if ((ifp = if_get(session->ifindex)) == NULL)
goto drop;
#if NBPFILTER > 0
if (ifp->if_bpf)
bpf_mtap_af(ifp->if_bpf, AF_INET6, m0, BPF_DIRECTION_IN);
#endif
counters_pkt(ifp->if_counters, ifc_ipackets, ifc_ibytes, len);
counters_pkt(session->stat_counters, pxc_ipackets, pxc_ibytes, len);
ipv6_input(ifp, m0);
if_put(ifp);
return;
drop:
m_freem(m0);
counters_inc(session->stat_counters, pxc_ierrors);
}
#endif
struct mbuf *
pipex_common_input(struct pipex_session *session, struct mbuf *m0, int hlen,
int plen, int locked)
{
int proto, ppphlen;
u_char code;
if ((m0->m_pkthdr.len < hlen + PIPEX_PPPMINLEN) ||
(plen < PIPEX_PPPMINLEN)) {
if (locked)
mtx_leave(&session->pxs_mtx);
goto drop;
}
proto = pipex_ppp_proto(m0, session, hlen, &ppphlen);
switch (proto) {
#ifdef PIPEX_MPPE
case PPP_CCP:
code = 0;
KASSERT(m0->m_pkthdr.len >= hlen + ppphlen + 1);
m_copydata(m0, hlen + ppphlen, 1, &code);
if (code != CCP_RESETREQ && code != CCP_RESETACK)
goto not_ours;
break;
case PPP_COMP:
if (pipex_session_is_mppe_accepted(session))
break;
goto not_ours;
#endif
case PPP_IP:
#ifdef INET6
case PPP_IPV6:
#endif
break;
default:
goto not_ours;
}
if (locked)
mtx_leave(&session->pxs_mtx);
/* ok, The packet is for PIPEX */
m_adj(m0, hlen);/* cut off the tunnel protocol header */
/* ensure the mbuf length equals the PPP frame length */
if (m0->m_pkthdr.len < plen)
goto drop;
if (m0->m_pkthdr.len > plen) {
if (m0->m_len == m0->m_pkthdr.len) {
m0->m_len = plen;
m0->m_pkthdr.len = plen;
} else
m_adj(m0, plen - m0->m_pkthdr.len);
}
pipex_ppp_input(m0, session, 0);
return (NULL);
drop:
m_freem(m0);
counters_inc(session->stat_counters, pxc_ierrors);
return (NULL);
not_ours:
return (m0); /* Not to be handled by PIPEX */
}
/*
* pipex_ppp_proto
*/
int
pipex_ppp_proto(struct mbuf *m0, struct pipex_session *session, int off,
int *hlenp)
{
int proto;
u_char *cp, pktbuf[4];
KASSERT(m0->m_pkthdr.len > sizeof(pktbuf));
m_copydata(m0, off, sizeof(pktbuf), pktbuf);
cp = pktbuf;
if (pipex_session_has_acf(session)) {
if (cp[0] == PPP_ALLSTATIONS && cp[1] == PPP_UI)
cp += 2;
#ifdef PIPEX_DEBUG
else if (!pipex_session_is_acfc_accepted(session))
PIPEX_DBG((session, LOG_DEBUG,
"no acf but acfc is not accepted by the peer."));
#endif
}
if ((*cp & 0x01) != 0) {
if (!pipex_session_is_pfc_accepted(session)) {
PIPEX_DBG((session, LOG_DEBUG, "Received a broken ppp "
"frame. No protocol field. %02x-%02x",
cp[0], cp[1]));
return (-1);
}
GETCHAR(proto, cp);
} else
GETSHORT(proto, cp);
if (hlenp != NULL)
*hlenp = cp - pktbuf;
return (proto);
}
#ifdef PIPEX_PPPOE
/***********************************************************************
* PPPoE
***********************************************************************/
static const u_char pipex_pppoe_padding[ETHERMIN];
/*
* pipex_pppoe_lookup_session
*/
struct pipex_session *
pipex_pppoe_lookup_session(struct mbuf *m0)
{
struct pipex_session *session;
struct pipex_pppoe_header pppoe;
/* short packet */
if (m0->m_pkthdr.len < (sizeof(struct ether_header) + sizeof(pppoe)))
return (NULL);
m_copydata(m0, sizeof(struct ether_header),
sizeof(struct pipex_pppoe_header), &pppoe);
pppoe.session_id = ntohs(pppoe.session_id);
session = pipex_lookup_by_session_id(PIPEX_PROTO_PPPOE,
pppoe.session_id);
#ifdef PIPEX_DEBUG
if (session == NULL)
PIPEX_DBG((NULL, LOG_DEBUG, "<%s> session not found (id=%d)",
__func__, pppoe.session_id));
#endif
if (session && session->proto.pppoe.over_ifidx !=
m0->m_pkthdr.ph_ifidx) {
pipex_rele_session(session);
session = NULL;
}
return (session);
}
struct mbuf *
pipex_pppoe_input(struct mbuf *m0, struct pipex_session *session)
{
int hlen;
struct pipex_pppoe_header pppoe;
/* already checked at pipex_pppoe_lookup_session */
KASSERT(m0->m_pkthdr.len >= (sizeof(struct ether_header) +
sizeof(pppoe)));
m_copydata(m0, sizeof(struct ether_header),
sizeof(struct pipex_pppoe_header), &pppoe);
hlen = sizeof(struct ether_header) + sizeof(struct pipex_pppoe_header);
m0 = pipex_common_input(session, m0, hlen, ntohs(pppoe.length), 0);
if (m0 == NULL)
return (NULL);
m_freem(m0);
counters_inc(session->stat_counters, pxc_ierrors);
return (NULL);
}
/*
* pipex_ppope_output
*/
void
pipex_pppoe_output(struct mbuf *m0, struct pipex_session *session)
{
struct pipex_pppoe_header *pppoe;
int len, padlen;
/* save length for pppoe header */
len = m0->m_pkthdr.len;
/* prepend protocol header */
M_PREPEND(m0, sizeof(struct pipex_pppoe_header), M_NOWAIT);
if (m0 == NULL) {
PIPEX_DBG((NULL, LOG_ERR,
"<%s> cannot prepend header.", __func__));
counters_inc(session->stat_counters, pxc_oerrors);
return;
}
padlen = ETHERMIN - m0->m_pkthdr.len;
if (padlen > 0)
m_copyback(m0, m0->m_pkthdr.len, padlen, pipex_pppoe_padding,
M_NOWAIT);
/* setup pppoe header information */
pppoe = mtod(m0, struct pipex_pppoe_header *);
pppoe->vertype = PIPEX_PPPOE_VERTYPE;
pppoe->code = PIPEX_PPPOE_CODE_SESSION;
pppoe->session_id = htons(session->session_id);
pppoe->length = htons(len);
m0->m_pkthdr.ph_ifidx = session->proto.pppoe.over_ifidx;
refcnt_take(&session->pxs_refcnt);
m0->m_pkthdr.ph_cookie = session;
m0->m_flags &= ~(M_BCAST|M_MCAST);
if (mq_enqueue(&pipexoutq, m0) != 0) {
counters_inc(session->stat_counters, pxc_oerrors);
pipex_rele_session(session);
} else
schednetisr(NETISR_PIPEX);
}
#endif /* PIPEX_PPPOE */
#ifdef PIPEX_PPTP
/***********************************************************************
* PPTP
***********************************************************************/
void
pipex_pptp_output(struct mbuf *m0, struct pipex_session *session,
int has_seq, int has_ack)
{
int len, reqlen;
struct pipex_gre_header *gre = NULL;
struct ip *ip;
u_char *cp;
MUTEX_ASSERT_LOCKED(&session->pxs_mtx);
reqlen = PIPEX_IPGRE_HDRLEN + (has_seq + has_ack) * 4;
len = 0;
if (m0 != NULL) {
/* save length for gre header */
len = m0->m_pkthdr.len;
/* prepend protocol header */
M_PREPEND(m0, reqlen, M_NOWAIT);
if (m0 == NULL)
goto drop;
} else {
MGETHDR(m0, M_DONTWAIT, MT_DATA);
if (m0 && reqlen > MHLEN) {
MCLGET(m0, M_DONTWAIT);
if ((m0->m_flags & M_EXT) == 0) {
m_freem(m0);
m0 = NULL;
}
}
if (m0 == NULL)
goto drop;
m0->m_pkthdr.len = m0->m_len = reqlen;
}
/* setup ip header information */
ip = mtod(m0, struct ip *);
ip->ip_len = htons(m0->m_pkthdr.len);
ip->ip_off = 0;
ip->ip_ttl = MAXTTL;
ip->ip_p = IPPROTO_GRE;
ip->ip_tos = 0;
ip->ip_src = session->local.sin4.sin_addr;
ip->ip_dst = session->peer.sin4.sin_addr;
#if NPF > 0
pf_pkt_addr_changed(m0);
#endif
/* setup gre(ver1) header information */
gre = PIPEX_SEEK_NEXTHDR(ip, sizeof(struct ip),
struct pipex_gre_header *);
gre->type = htons(PIPEX_GRE_PROTO_PPP);
gre->call_id = htons(session->peer_session_id);
gre->flags = PIPEX_GRE_KFLAG | PIPEX_GRE_VER; /* do htons later */
gre->len = htons(len);
cp = PIPEX_SEEK_NEXTHDR(gre, sizeof(struct pipex_gre_header),u_char *);
if (has_seq) {
gre->flags |= PIPEX_GRE_SFLAG;
PUTLONG(session->proto.pptp.snd_nxt, cp);
session->proto.pptp.snd_nxt++;
session->proto.pptp.snd_gap++;
}
if (has_ack) {
gre->flags |= PIPEX_GRE_AFLAG;
session->proto.pptp.rcv_acked = session->proto.pptp.rcv_nxt - 1;
PUTLONG(session->proto.pptp.rcv_acked, cp);
}
gre->flags = htons(gre->flags);
m0->m_pkthdr.ph_ifidx = session->ifindex;
ip_send(m0);
if (len > 0) { /* network layer only */
/* countup statistics */
counters_pkt(session->stat_counters, pxc_opackets,
pxc_obytes, len);
}
return;
drop:
counters_inc(session->stat_counters, pxc_oerrors);
}
struct pipex_session *
pipex_pptp_lookup_session(struct mbuf *m0)
{
struct pipex_session *session;
struct pipex_gre_header gre;
struct ip ip;
uint16_t flags;
uint16_t id;
int hlen;
if (m0->m_pkthdr.len < PIPEX_IPGRE_HDRLEN) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> packet length is too short", __func__));
goto not_ours;
}
/* get ip header info */
m_copydata(m0, 0, sizeof(struct ip), &ip);
hlen = ip.ip_hl << 2;
/*
* m0 has already passed ip_input(), so there is
* no necessity for ip packet inspection.
*/
/* get gre flags */
m_copydata(m0, hlen, sizeof(gre), &gre);
flags = ntohs(gre.flags);
/* gre version must be '1' */
if ((flags & PIPEX_GRE_VERMASK) != PIPEX_GRE_VER) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> gre header wrong version.", __func__));
goto not_ours;
}
/* gre keys must be present */
if ((flags & PIPEX_GRE_KFLAG) == 0) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> gre header has no keys.", __func__));
goto not_ours;
}
/* flag check */
if ((flags & PIPEX_GRE_UNUSEDFLAGS) != 0) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> gre header has unused flags at pptp.", __func__));
goto not_ours;
}
/* lookup pipex session table */
id = ntohs(gre.call_id);
session = pipex_lookup_by_session_id(PIPEX_PROTO_PPTP, id);
#ifdef PIPEX_DEBUG
if (session == NULL) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> session not found (id=%d)", __func__, id));
goto not_ours;
}
#endif
return (session);
not_ours:
return (NULL);
}
struct mbuf *
pipex_pptp_input(struct mbuf *m0, struct pipex_session *session)
{
int hlen, has_seq, has_ack, nseq;
const char *reason = "";
u_char *cp, *seqp = NULL, *ackp = NULL;
uint32_t flags, seq = 0, ack = 0;
struct ip *ip;
struct pipex_gre_header *gre;
struct pipex_pptp_session *pptp_session;
int rewind = 0;
KASSERT(m0->m_pkthdr.len >= PIPEX_IPGRE_HDRLEN);
pptp_session = &session->proto.pptp;
/* get ip header */
ip = mtod(m0, struct ip *);
hlen = ip->ip_hl << 2;
/* seek gre header */
gre = PIPEX_SEEK_NEXTHDR(ip, hlen, struct pipex_gre_header *);
flags = ntohs(gre->flags);
/* pullup for seek sequences in header */
has_seq = (flags & PIPEX_GRE_SFLAG) ? 1 : 0;
has_ack = (flags & PIPEX_GRE_AFLAG) ? 1 : 0;
hlen = PIPEX_IPGRE_HDRLEN + 4 * (has_seq + has_ack);
if (m0->m_len < hlen) {
m0 = m_pullup(m0, hlen);
if (m0 == NULL) {
PIPEX_DBG((session, LOG_DEBUG, "pullup failed."));
goto drop;
}
}
/* check sequence */
cp = PIPEX_SEEK_NEXTHDR(gre, sizeof(struct pipex_gre_header),u_char *);
if (has_seq) {
seqp = cp;
GETLONG(seq, cp);
}
mtx_enter(&session->pxs_mtx);
if (has_ack) {
ackp = cp;
GETLONG(ack, cp);
if (ack + 1 == pptp_session->snd_una) {
/* ack has not changed before */
} else if (SEQ32_LT(ack, pptp_session->snd_una)) {
/* OoO ack packets should not be dropped. */
rewind = 1;
} else if (SEQ32_GT(ack, pptp_session->snd_nxt)) {
reason = "ack for unknown sequence";
goto out_seq;
} else
pptp_session->snd_una = ack + 1;
}
if (!has_seq) {
/* ack only packet */
goto not_ours;
}
if (SEQ32_LT(seq, pptp_session->rcv_nxt)) {
rewind = 1;
if (SEQ32_LT(seq,
pptp_session->rcv_nxt - PIPEX_REWIND_LIMIT)) {
reason = "out of sequence";
goto out_seq;
}
} else if (SEQ32_GE(seq, pptp_session->rcv_nxt +
pptp_session->maxwinsz)) {
pipex_session_log(session, LOG_DEBUG,
"received packet caused window overflow. seq=%u(%u-%u)"
"may lost %d packets.", seq, pptp_session->rcv_nxt,
pptp_session->rcv_nxt + pptp_session->maxwinsz,
(int)SEQ32_SUB(seq, pptp_session->rcv_nxt));
}
seq++;
nseq = SEQ32_SUB(seq, pptp_session->rcv_nxt);
if (!rewind) {
pptp_session->rcv_nxt = seq;
if (SEQ32_SUB(seq, pptp_session->rcv_acked) >
roundup(pptp_session->winsz, 2) / 2) /* Send ack only packet. */
pipex_pptp_output(NULL, session, 0, 1);
}
/*
* The following pipex_common_input() will release `pxs_mtx'
* deep within if the packet will be consumed. In the error
* path lock will be held all the time. So increment `rcv_gap'
* here, and on the error path back it out, no atomicity will
* be lost in all cases.
*/
if (!rewind)
session->proto.pptp.rcv_gap += nseq;
m0 = pipex_common_input(session, m0, hlen, ntohs(gre->len), 1);
if (m0 == NULL) {
/*
* pipex_common_input() releases lock if the
* packet was consumed.
*/
return (NULL);
}
if (rewind)
goto out_seq;
else {
/* The packet is not ours, back out `rcv_gap'. */
session->proto.pptp.rcv_gap -= nseq;
}
not_ours:
seq--; /* revert original seq value */
/*
* overwrite sequence numbers to adjust a gap between pipex and
* userland.
*/
if (seqp != NULL) {
seq -= pptp_session->rcv_gap;
PUTLONG(seq, seqp);
}
if (ackp != NULL) {
if (pptp_session->snd_nxt == pptp_session->snd_una) {
ack -= session->proto.pptp.snd_gap;
pptp_session->ul_snd_una = ack;
} else {
/*
* There are sending packets they are not acked.
* In this situation, (ack - snd_gap) may points
* before sending window of userland. So we don't
* update the ack number.
*/
ack = pptp_session->ul_snd_una;
}
PUTLONG(ack, ackp);
}
mtx_leave(&session->pxs_mtx);
return (m0);
out_seq:
pipex_session_log(session, LOG_DEBUG,
"Received bad data packet: %s: seq=%u(%u-%u) ack=%u(%u-%u)",
reason, seq, pptp_session->rcv_nxt,
pptp_session->rcv_nxt + pptp_session->maxwinsz,
ack, pptp_session->snd_una,
pptp_session->snd_nxt);
mtx_leave(&session->pxs_mtx);
/* FALLTHROUGH */
drop:
m_freem(m0);
counters_inc(session->stat_counters, pxc_ierrors);
return (NULL);
}
struct pipex_session *
pipex_pptp_userland_lookup_session_ipv4(struct mbuf *m0, struct in_addr dst)
{
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_addr = dst;
return pipex_pptp_userland_lookup_session(m0, sintosa(&sin));
}
#ifdef INET6
struct pipex_session *
pipex_pptp_userland_lookup_session_ipv6(struct mbuf *m0, struct in6_addr dst)
{
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_len = sizeof(sin6);
sin6.sin6_family = AF_INET6;
in6_recoverscope(&sin6, &dst);
return pipex_pptp_userland_lookup_session(m0, sin6tosa(&sin6));
}
#endif
struct pipex_session *
pipex_pptp_userland_lookup_session(struct mbuf *m0, struct sockaddr *sa)
{
struct pipex_gre_header gre;
struct pipex_hash_head *list;
struct pipex_session *session;
uint16_t id, flags;
/* pullup */
if (m0->m_pkthdr.len < sizeof(gre)) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> packet length is too short", __func__));
return (NULL);
}
/* get flags */
m_copydata(m0, 0, sizeof(struct pipex_gre_header), &gre);
flags = ntohs(gre.flags);
/* gre version must be '1' */
if ((flags & PIPEX_GRE_VERMASK) != PIPEX_GRE_VER) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> gre header wrong version.", __func__));
return (NULL);
}
/* gre keys must be present */
if ((flags & PIPEX_GRE_KFLAG) == 0) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> gre header has no keys.", __func__));
return (NULL);
}
/* lookup pipex session table */
id = ntohs(gre.call_id);
mtx_enter(&pipex_list_mtx);
list = PIPEX_PEER_ADDR_HASHTABLE(pipex_sockaddr_hash_key(sa));
LIST_FOREACH(session, list, peer_addr_chain) {
if (pipex_sockaddr_compar_addr(&session->peer.sa, sa) != 0)
continue;
if (session->peer_session_id == id)
break;
}
if (session != NULL)
refcnt_take(&session->pxs_refcnt);
mtx_leave(&pipex_list_mtx);
#ifdef PIPEX_DEBUG
if (session == NULL) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> session not found (,call_id=%d)",
__func__, (int)gre.call_id));
}
#endif
return (session);
}
/*
* pipex_pptp_userland_output
*/
struct mbuf *
pipex_pptp_userland_output(struct mbuf *m0, struct pipex_session *session)
{
int len;
struct pipex_gre_header *gre, gre0;
uint16_t flags;
u_char *cp, *cp0;
uint32_t val32;
len = sizeof(struct pipex_gre_header);
m_copydata(m0, 0, len, &gre0);
gre = &gre0;
flags = ntohs(gre->flags);
if ((flags & PIPEX_GRE_SFLAG) != 0)
len += 4;
if ((flags & PIPEX_GRE_AFLAG) != 0)
len += 4;
/* check length */
PIPEX_PULLUP(m0, len);
if (m0 == NULL) {
PIPEX_DBG((session, LOG_DEBUG, "gre header is too short."));
return (NULL);
}
gre = mtod(m0, struct pipex_gre_header *);
cp = PIPEX_SEEK_NEXTHDR(gre, sizeof(struct pipex_gre_header), u_char *);
mtx_enter(&session->pxs_mtx);
/*
* overwrite sequence numbers to adjust a gap between pipex and
* userland.
*/
if ((flags & PIPEX_GRE_SFLAG) != 0) {
cp0 = cp;
GETLONG(val32, cp);
val32 += session->proto.pptp.snd_gap;
PUTLONG(val32, cp0);
session->proto.pptp.snd_nxt++;
}
if ((flags & PIPEX_GRE_AFLAG) != 0) {
cp0 = cp;
GETLONG(val32, cp);
val32 += session->proto.pptp.rcv_gap;
PUTLONG(val32, cp0);
if (SEQ32_GT(val32, session->proto.pptp.rcv_acked))
session->proto.pptp.rcv_acked = val32;
}
mtx_leave(&session->pxs_mtx);
return (m0);
}
#endif /* PIPEX_PPTP */
#ifdef PIPEX_L2TP
/***********************************************************************
* L2TP support
***********************************************************************/
void
pipex_l2tp_output(struct mbuf *m0, struct pipex_session *session)
{
int hlen, plen, datalen;
struct pipex_l2tp_header *l2tp = NULL;
struct pipex_l2tp_seq_header *seq = NULL;
struct udphdr *udp;
struct ip *ip;
#ifdef INET6
struct ip6_hdr *ip6;
#endif
struct m_tag *mtag;
hlen = sizeof(struct pipex_l2tp_header) +
((pipex_session_is_l2tp_data_sequencing_on(session))
? sizeof(struct pipex_l2tp_seq_header) : 0) +
sizeof(struct udphdr) +
#ifdef INET6
((session->peer.sin6.sin6_family == AF_INET6)
? sizeof(struct ip6_hdr) : sizeof(struct ip));
#else
sizeof(struct ip);
#endif
datalen = 0;
if (m0 != NULL) {
datalen = m0->m_pkthdr.len;
M_PREPEND(m0, hlen, M_NOWAIT);
if (m0 == NULL)
goto drop;
} else {
MGETHDR(m0, M_DONTWAIT, MT_DATA);
if (m0 == NULL)
goto drop;
KASSERT(hlen <= MHLEN);
m0->m_pkthdr.len = m0->m_len = hlen;
}
#ifdef INET6
hlen = (session->peer.sin6.sin6_family == AF_INET6)
? sizeof(struct ip6_hdr) : sizeof(struct ip);
#else
hlen = sizeof(struct ip);
#endif
plen = datalen + sizeof(struct pipex_l2tp_header) +
((pipex_session_is_l2tp_data_sequencing_on(session))
? sizeof(struct pipex_l2tp_seq_header) : 0);
l2tp = (struct pipex_l2tp_header *)
(mtod(m0, caddr_t) + hlen + sizeof(struct udphdr));
l2tp->flagsver = PIPEX_L2TP_VER | PIPEX_L2TP_FLAG_LENGTH;
l2tp->length = htons(plen);
l2tp->tunnel_id = htons(session->proto.l2tp.peer_tunnel_id);
l2tp->session_id = htons(session->peer_session_id);
if (pipex_session_is_l2tp_data_sequencing_on(session)) {
seq = (struct pipex_l2tp_seq_header *)(l2tp + 1);
l2tp->flagsver |= PIPEX_L2TP_FLAG_SEQUENCE;
mtx_enter(&session->pxs_mtx);
seq->ns = htons(session->proto.l2tp.ns_nxt);
session->proto.l2tp.ns_nxt++;
session->proto.l2tp.ns_gap++;
session->proto.l2tp.nr_acked = session->proto.l2tp.nr_nxt - 1;
seq->nr = htons(session->proto.l2tp.nr_acked);
mtx_leave(&session->pxs_mtx);
}
l2tp->flagsver = htons(l2tp->flagsver);
plen += sizeof(struct udphdr);
udp = (struct udphdr *)(mtod(m0, caddr_t) + hlen);
udp->uh_sport = session->local.sin6.sin6_port;
udp->uh_dport = session->peer.sin6.sin6_port;
udp->uh_ulen = htons(plen);
udp->uh_sum = 0;
m0->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT;
m0->m_pkthdr.ph_ifidx = session->ifindex;
#if NPF > 0
pf_pkt_addr_changed(m0);
#endif
switch (session->peer.sin6.sin6_family) {
case AF_INET:
ip = mtod(m0, struct ip *);
ip->ip_p = IPPROTO_UDP;
ip->ip_src = session->local.sin4.sin_addr;
ip->ip_dst = session->peer.sin4.sin_addr;
ip->ip_len = htons(hlen + plen);
ip->ip_ttl = MAXTTL;
ip->ip_tos = 0;
ip->ip_off = 0;
mtx_enter(&session->pxs_mtx);
if (session->proto.l2tp.ipsecflowinfo > 0) {
if ((mtag = m_tag_get(PACKET_TAG_IPSEC_FLOWINFO,
sizeof(u_int32_t), M_NOWAIT)) == NULL) {
mtx_leave(&session->pxs_mtx);
goto drop;
}
*(u_int32_t *)(mtag + 1) =
session->proto.l2tp.ipsecflowinfo;
m_tag_prepend(m0, mtag);
}
mtx_leave(&session->pxs_mtx);
ip_send(m0);
break;
#ifdef INET6
case AF_INET6:
ip6 = mtod(m0, struct ip6_hdr *);
ip6->ip6_flow = 0;
ip6->ip6_vfc &= ~IPV6_VERSION_MASK;
ip6->ip6_vfc |= IPV6_VERSION;
ip6->ip6_nxt = IPPROTO_UDP;
ip6->ip6_src = session->local.sin6.sin6_addr;
in6_embedscope(&ip6->ip6_dst, &session->peer.sin6, NULL, NULL);
/* ip6->ip6_plen will be filled in ip6_output. */
ip6_send(m0);
break;
#endif
}
udpstat_inc(udps_opackets);
if (datalen > 0) { /* network layer only */
/* countup statistics */
counters_pkt(session->stat_counters, pxc_opackets,
pxc_obytes, datalen);
}
return;
drop:
m_freem(m0);
counters_inc(session->stat_counters, pxc_oerrors);
}
struct pipex_session *
pipex_l2tp_lookup_session(struct mbuf *m0, int off)
{
struct pipex_session *session;
uint16_t flags, session_id, ver;
u_char *cp, buf[PIPEX_L2TP_MINLEN];
if (m0->m_pkthdr.len < off + PIPEX_L2TP_MINLEN) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> packet length is too short", __func__));
goto not_ours;
}
/* get first 16bits of L2TP */
m_copydata(m0, off, sizeof(buf), buf);
cp = buf;
GETSHORT(flags, cp);
ver = flags & PIPEX_L2TP_VER_MASK;
/* l2tp version must be '2' */
if (ver != PIPEX_L2TP_VER) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> l2tp header wrong version %u.", __func__, ver));
goto not_ours;
}
if ((flags & PIPEX_L2TP_FLAG_TYPE) != 0)
goto not_ours;
if (flags & PIPEX_L2TP_FLAG_LENGTH)
cp += 2; /* skip length field */
cp += 2; /* skip tunnel-id field */
GETSHORT(session_id, cp); /* get session-id field */
/* lookup pipex session table */
session = pipex_lookup_by_session_id(PIPEX_PROTO_L2TP, session_id);
#ifdef PIPEX_DEBUG
if (session == NULL) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> session not found (id=%d)", __func__, session_id));
goto not_ours;
}
#endif
return (session);
not_ours:
return (NULL);
}
struct mbuf *
pipex_l2tp_input(struct mbuf *m0, int off0, struct pipex_session *session,
uint32_t ipsecflowinfo)
{
struct pipex_l2tp_session *l2tp_session;
int length = 0, offset = 0, hlen, nseq;
u_char *cp, *nsp = NULL, *nrp = NULL;
uint16_t flags, ns = 0, nr = 0;
int rewind = 0;
mtx_enter(&session->pxs_mtx);
l2tp_session = &session->proto.l2tp;
l2tp_session->ipsecflowinfo = ipsecflowinfo;
m_copydata(m0, off0, sizeof(flags), &flags);
flags = ntohs(flags) & PIPEX_L2TP_FLAG_MASK;
KASSERT((flags & PIPEX_L2TP_FLAG_TYPE) == 0);
hlen = 2; /* flags and version fields */
if (flags & PIPEX_L2TP_FLAG_LENGTH) /* length */
hlen += 2;
hlen += 4; /* tunnel-id and session-id */
if (flags & PIPEX_L2TP_FLAG_SEQUENCE) /* ns and nr */
hlen += 4;
if (flags & PIPEX_L2TP_FLAG_OFFSET) /* offset */
hlen += 2;
PIPEX_PULLUP(m0, off0 + hlen);
if (m0 == NULL)
goto drop;
cp = mtod(m0, u_char *) + off0;
cp += 2; /* flags and version */
if (flags & PIPEX_L2TP_FLAG_LENGTH)
GETSHORT(length, cp);
else
length = m0->m_pkthdr.len - off0;
cp += 4; /* skip tunnel-id and session-id field */
/* pullup for seek sequences in header */
nseq = 0;
if (flags & PIPEX_L2TP_FLAG_SEQUENCE) {
nsp = cp;
GETSHORT(ns, cp);
nrp = cp;
GETSHORT(nr, cp);
nr++;
if (SEQ16_GT(nr, l2tp_session->ns_una) &&
SEQ16_LE(nr, l2tp_session->ns_nxt))
/* update 'ns_una' only if the ns is in valid range */
l2tp_session->ns_una = nr;
if (SEQ16_LT(ns, l2tp_session->nr_nxt)) {
rewind = 1;
if (SEQ16_LT(ns,
l2tp_session->nr_nxt - PIPEX_REWIND_LIMIT))
goto out_seq;
}
ns++;
nseq = SEQ16_SUB(ns, l2tp_session->nr_nxt);
if (!rewind)
l2tp_session->nr_nxt = ns;
}
if (flags & PIPEX_L2TP_FLAG_OFFSET)
GETSHORT(offset, cp);
length -= hlen + offset;
hlen += off0 + offset;
/*
* The following pipex_common_input() will release `pxs_mtx'
* deep within if the packet will be consumed. In the error
* path lock will be held all the time. So increment `nr_gap'
* here, and on the error path back it out, no atomicity will
* be lost in all cases.
*/
if (!rewind)
session->proto.l2tp.nr_gap += nseq;
m0 = pipex_common_input(session, m0, hlen, length, 1);
if (m0 == NULL) {
/*
* pipex_common_input() releases lock if the
* packet was consumed.
*/
return (NULL);
}
if (rewind)
goto out_seq;
else {
/* The packet is not ours, backout `nr_gap'. */
session->proto.l2tp.nr_gap -= nseq;
}
/*
* overwrite sequence numbers to adjust a gap between pipex and
* userland.
*/
if (flags & PIPEX_L2TP_FLAG_SEQUENCE) {
--ns; --nr; /* revert original values */
ns -= l2tp_session->nr_gap;
PUTSHORT(ns, nsp);
if (l2tp_session->ns_nxt == l2tp_session->ns_una) {
nr -= l2tp_session->ns_gap;
l2tp_session->ul_ns_una = nr;
} else {
/*
* There are sending packets they are not acked.
* In this situation, (ack - snd_gap) may points
* before sending window of userland. So we don't
* update the ack number.
*/
nr = l2tp_session->ul_ns_una;
}
PUTSHORT(nr, nrp);
}
mtx_leave(&session->pxs_mtx);
return (m0);
out_seq:
pipex_session_log(session, LOG_DEBUG,
"Received bad data packet: out of sequence: seq=%u(%u-) "
"ack=%u(%u-%u)", ns, l2tp_session->nr_nxt, nr, l2tp_session->ns_una,
l2tp_session->ns_nxt);
/* FALLTHROUGH */
drop:
mtx_leave(&session->pxs_mtx);
m_freem(m0);
counters_inc(session->stat_counters, pxc_ierrors);
return (NULL);
}
struct pipex_session *
pipex_l2tp_userland_lookup_session_ipv4(struct mbuf *m0, struct in_addr dst)
{
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_len = sizeof(sin);
sin.sin_family = AF_INET;
sin.sin_addr = dst;
return pipex_l2tp_userland_lookup_session(m0, sintosa(&sin));
}
#ifdef INET6
struct pipex_session *
pipex_l2tp_userland_lookup_session_ipv6(struct mbuf *m0, struct in6_addr dst)
{
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_len = sizeof(sin6);
sin6.sin6_family = AF_INET6;
in6_recoverscope(&sin6, &dst);
return pipex_l2tp_userland_lookup_session(m0, sin6tosa(&sin6));
}
#endif
struct pipex_session *
pipex_l2tp_userland_lookup_session(struct mbuf *m0, struct sockaddr *sa)
{
struct pipex_l2tp_header l2tp;
struct pipex_hash_head *list;
struct pipex_session *session;
uint16_t session_id, tunnel_id, flags;
if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6)
return (NULL);
/* pullup */
if (m0->m_pkthdr.len < sizeof(l2tp)) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> packet length is too short", __func__));
return (NULL);
}
/* get flags */
m_copydata(m0, 0, sizeof(l2tp), &l2tp);
flags = ntohs(l2tp.flagsver);
/* l2tp version must be '2' */
if ((flags & PIPEX_L2TP_VER_MASK) != PIPEX_L2TP_VER) {
PIPEX_DBG((NULL, LOG_DEBUG,
"<%s> l2tp header wrong version.", __func__));
return (NULL);
}
/* We need L2TP data messages only */
if ((flags & PIPEX_L2TP_FLAG_TYPE) != 0)
return (NULL);
/* No need to hook packets that don't have the sequence field */
if ((flags & PIPEX_L2TP_FLAG_SEQUENCE) == 0)
return (NULL);
session_id = ntohs(l2tp.session_id);
tunnel_id = ntohs(l2tp.tunnel_id);
mtx_enter(&pipex_list_mtx);
list = PIPEX_PEER_ADDR_HASHTABLE(pipex_sockaddr_hash_key(sa));
LIST_FOREACH(session, list, peer_addr_chain) {
if (pipex_sockaddr_compar_addr(&session->peer.sa, sa) != 0)
continue;
if (session->proto.l2tp.peer_tunnel_id != tunnel_id)
continue;
if (session->peer_session_id == session_id)
break;
}
if (session != NULL)
refcnt_take(&session->pxs_refcnt);
mtx_leave(&pipex_list_mtx);
#ifdef PIPEX_DEBUG
if (session == NULL) {
PIPEX_DBG((NULL, LOG_DEBUG, "<%s> session not found "
"(tunnel_id=%d, session_id=%d)", __func__,
tunnel_id, session_id));
}
#endif
return (session);
}
struct mbuf *
pipex_l2tp_userland_output(struct mbuf *m0, struct pipex_session *session)
{
struct pipex_l2tp_header *l2tp;
struct pipex_l2tp_seq_header *seq;
uint16_t ns, nr;
/* check length */
PIPEX_PULLUP(m0, sizeof(struct pipex_l2tp_header) +
sizeof(struct pipex_l2tp_seq_header));
if (m0 == NULL)
return (NULL);
l2tp = mtod(m0, struct pipex_l2tp_header *);
KASSERT(ntohs(l2tp->flagsver) & PIPEX_L2TP_FLAG_SEQUENCE);
/*
* overwrite sequence numbers to adjust a gap between pipex and
* userland.
*/
seq = (struct pipex_l2tp_seq_header *)(l2tp + 1);
ns = ntohs(seq->ns);
nr = ntohs(seq->nr);
mtx_enter(&session->pxs_mtx);
ns += session->proto.l2tp.ns_gap;
seq->ns = htons(ns);
session->proto.l2tp.ns_nxt++;
nr += session->proto.l2tp.nr_gap;
seq->nr = htons(nr);
if (SEQ16_GT(nr, session->proto.l2tp.nr_acked))
session->proto.l2tp.nr_acked = nr;
mtx_leave(&session->pxs_mtx);
return (m0);
}
#endif /* PIPEX_L2TP */
#ifdef PIPEX_MPPE
/**********************************************************************
* MPPE
***********************************************************************/
#define PIPEX_COHERENCY_CNT_MASK 0x0fff
static inline int
pipex_mppe_setkey(struct pipex_mppe *mppe)
{
rc4_keysetup(&mppe->rc4ctx, mppe->session_key, mppe->keylen);
return (0);
}
static inline int
pipex_mppe_setoldkey(struct pipex_mppe *mppe, uint16_t coher_cnt)
{
KASSERT(mppe->old_session_keys != NULL);
rc4_keysetup(&mppe->rc4ctx,
mppe->old_session_keys[coher_cnt & PIPEX_MPPE_OLDKEYMASK],
mppe->keylen);
return (0);
}
static inline void
pipex_mppe_crypt(struct pipex_mppe *mppe, int len, u_char *indata,
u_char *outdata)
{
rc4_crypt(&mppe->rc4ctx, indata, outdata, len);
}
void
pipex_mppe_init(struct pipex_mppe *mppe, int stateless, int keylenbits,
u_char *master_key, int has_oldkey)
{
memset(mppe, 0, sizeof(struct pipex_mppe));
mtx_init(&mppe->pxm_mtx, IPL_SOFTNET);
if (stateless)
mppe->flags |= PIPEX_MPPE_STATELESS;
if (has_oldkey)
mppe->old_session_keys =
pool_get(&mppe_key_pool, PR_WAITOK);
else
mppe->old_session_keys = NULL;
memcpy(mppe->master_key, master_key, sizeof(mppe->master_key));
mppe->keylenbits = keylenbits;
switch (keylenbits) {
case 40:
case 56:
mppe->keylen = 8;
break;
case 128:
mppe->keylen = 16;
break;
}
GetNewKeyFromSHA(mppe->master_key, mppe->master_key, mppe->keylen,
mppe->session_key);
pipex_mppe_reduce_key(mppe);
pipex_mppe_setkey(mppe);
}
void
pipex_session_init_mppe_recv(struct pipex_session *session, int stateless,
int keylenbits, u_char *master_key)
{
pipex_mppe_init(&session->mppe_recv, stateless, keylenbits,
master_key, stateless);
session->ppp_flags |= PIPEX_PPP_MPPE_ACCEPTED;
}
void
pipex_session_init_mppe_send(struct pipex_session *session, int stateless,
int keylenbits, u_char *master_key)
{
pipex_mppe_init(&session->mppe_send, stateless, keylenbits,
master_key, 0);
session->ppp_flags |= PIPEX_PPP_MPPE_ENABLED;
}
#include <crypto/sha1.h>
static u_char SHAPad1[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}, SHAPad2[] = {
0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
};
void
GetNewKeyFromSHA(u_char *StartKey, u_char *SessionKey, int SessionKeyLength,
u_char *InterimKey)
{
u_char Digest[20];
SHA1_CTX Context;
SHA1Init(&Context);
SHA1Update(&Context, StartKey, SessionKeyLength);
SHA1Update(&Context, SHAPad1, 40);
SHA1Update(&Context, SessionKey, SessionKeyLength);
SHA1Update(&Context, SHAPad2, 40);
SHA1Final(Digest, &Context);
memcpy(InterimKey, Digest, SessionKeyLength);
}
void
pipex_mppe_reduce_key(struct pipex_mppe *mppe)
{
switch (mppe->keylenbits) {
case 40:
mppe->session_key[0] = 0xd1;
mppe->session_key[1] = 0x26;
mppe->session_key[2] = 0x9e;
break;
case 56:
mppe->session_key[0] = 0xd1;
break;
}
}
void
mppe_key_change(struct pipex_mppe *mppe)
{
u_char interim[16];
struct rc4_ctx keychg;
memset(&keychg, 0, sizeof(keychg));
GetNewKeyFromSHA(mppe->master_key, mppe->session_key, mppe->keylen,
interim);
rc4_keysetup(&keychg, interim, mppe->keylen);
rc4_crypt(&keychg, interim, mppe->session_key, mppe->keylen);
pipex_mppe_reduce_key(mppe);
if (mppe->old_session_keys) {
int idx = mppe->coher_cnt & PIPEX_MPPE_OLDKEYMASK;
memcpy(mppe->old_session_keys[idx],
mppe->session_key, PIPEX_MPPE_KEYLEN);
}
}
struct mbuf *
pipex_mppe_input(struct mbuf *m0, struct pipex_session *session)
{
int pktloss, encrypt, flushed, m, n, len;
struct pipex_mppe *mppe;
uint16_t coher_cnt;
struct mbuf *m1;
u_char *cp;
int rewind = 0;
/* pullup */
PIPEX_PULLUP(m0, sizeof(coher_cnt));
if (m0 == NULL)
goto drop;
mppe = &session->mppe_recv;
/* get header information */
cp = mtod(m0, u_char *);
GETSHORT(coher_cnt, cp);
flushed = ((coher_cnt & 0x8000) != 0) ? 1 : 0;
encrypt = ((coher_cnt & 0x1000) != 0) ? 1 : 0;
coher_cnt &= PIPEX_COHERENCY_CNT_MASK;
pktloss = 0;
mtx_enter(&mppe->pxm_mtx);
PIPEX_MPPE_DBG((session, LOG_DEBUG, "in coher_cnt=%03x %s%s",
mppe->coher_cnt, (flushed) ? "[flushed]" : "",
(encrypt) ? "[encrypt]" : ""));
if (encrypt == 0) {
mtx_leave(&mppe->pxm_mtx);
pipex_session_log(session, LOG_DEBUG,
"Received unexpected MPPE packet.(no ecrypt)");
goto drop;
}
/* adjust mbuf */
m_adj(m0, sizeof(coher_cnt));
/*
* L2TP data session may be used without sequencing, PPP frames may
* arrive in disorder. The 'coherency counter' of MPPE detects such
* situations, but we cannot distinguish between 'disorder' and
* 'packet loss' exactly.
*
* When 'coherency counter' detects lost packets greater than
* (4096 - 256), we treat as 'disorder' otherwise treat as
* 'packet loss'.
*/
{
int coher_cnt0;
coher_cnt0 = coher_cnt;
if (coher_cnt < mppe->coher_cnt)
coher_cnt0 += 0x1000;
if (coher_cnt0 - mppe->coher_cnt > 0x0f00) {
if ((mppe->flags & PIPEX_MPPE_STATELESS) == 0 ||
coher_cnt0 - mppe->coher_cnt
<= 0x1000 - PIPEX_MPPE_NOLDKEY) {
pipex_session_log(session, LOG_DEBUG,
"Workaround the out-of-sequence PPP framing problem: "
"%d => %d", mppe->coher_cnt, coher_cnt);
mtx_leave(&mppe->pxm_mtx);
goto drop;
}
rewind = 1;
}
}
if ((mppe->flags & PIPEX_MPPE_STATELESS) != 0) {
if (!rewind) {
mppe_key_change(mppe);
while (mppe->coher_cnt != coher_cnt) {
mppe->coher_cnt++;
mppe->coher_cnt &= PIPEX_COHERENCY_CNT_MASK;
mppe_key_change(mppe);
pktloss++;
}
}
pipex_mppe_setoldkey(mppe, coher_cnt);
} else {
if (flushed) {
if (coher_cnt < mppe->coher_cnt) {
coher_cnt += 0x1000;
}
pktloss += coher_cnt - mppe->coher_cnt;
m = mppe->coher_cnt / 256;
n = coher_cnt / 256;
while (m++ < n)
mppe_key_change(mppe);
coher_cnt &= PIPEX_COHERENCY_CNT_MASK;
mppe->coher_cnt = coher_cnt;
} else if (mppe->coher_cnt != coher_cnt) {
int ccp_id;
mtx_leave(&mppe->pxm_mtx);
/* Send CCP ResetReq */
PIPEX_DBG((session, LOG_DEBUG, "CCP SendResetReq"));
mtx_enter(&session->pxs_mtx);
ccp_id = session->ccp_id;
session->ccp_id++;
mtx_leave(&session->pxs_mtx);
pipex_ccp_output(session, CCP_RESETREQ, ccp_id);
goto drop;
}
if ((coher_cnt & 0xff) == 0xff) {
mppe_key_change(mppe);
flushed = 1;
}
if (flushed)
pipex_mppe_setkey(mppe);
}
if (pktloss > 1000) {
pipex_session_log(session, LOG_DEBUG,
"%d packets loss.", pktloss);
}
/* decrypt ppp payload */
for (m1 = m0; m1; m1 = m1->m_next) {
cp = mtod(m1, u_char *);
len = m1->m_len;
pipex_mppe_crypt(mppe, len, cp, cp);
}
if (!rewind) {
/* update coher_cnt */
mppe->coher_cnt++;
mppe->coher_cnt &= PIPEX_COHERENCY_CNT_MASK;
}
mtx_leave(&mppe->pxm_mtx);
if (m0->m_pkthdr.len < PIPEX_PPPMINLEN)
goto drop;
return (m0);
drop:
m_freem(m0);
return (NULL);
}
struct mbuf *
pipex_mppe_output(struct mbuf *m0, struct pipex_session *session,
uint16_t protocol)
{
int encrypt, flushed, len;
struct mppe_header {
uint16_t coher_cnt;
uint16_t protocol;
} __packed *hdr;
u_char *cp;
struct pipex_mppe *mppe;
struct mbuf *m;
mppe = &session->mppe_send;
/*
* create a deep-copy if the mbuf has a shared mbuf cluster.
* this is required to handle cases of tcp retransmission.
*/
for (m = m0; m != NULL; m = m->m_next) {
if (M_READONLY(m)) {
m = m_dup_pkt(m0, max_linkhdr, M_NOWAIT);
m_freem(m0);
if (m == NULL)
return (NULL);
m0 = m;
break;
}
}
/* prepend mppe header */
M_PREPEND(m0, sizeof(struct mppe_header), M_NOWAIT);
if (m0 == NULL)
return (NULL);
hdr = mtod(m0, struct mppe_header *);
hdr->protocol = protocol;
/* check coherency counter */
flushed = 0;
encrypt = 1;
mtx_enter(&mppe->pxm_mtx);
if ((mppe->flags & PIPEX_MPPE_STATELESS) != 0) {
flushed = 1;
mppe_key_change(mppe);
} else {
if ((mppe->coher_cnt % 0x100) == 0xff) {
flushed = 1;
mppe_key_change(mppe);
} else if ((mppe->flags & PIPEX_MPPE_RESETREQ) != 0) {
flushed = 1;
mppe->flags &= ~PIPEX_MPPE_RESETREQ;
}
}
if (flushed)
pipex_mppe_setkey(mppe);
PIPEX_MPPE_DBG((session, LOG_DEBUG, "out coher_cnt=%03x %s%s",
mppe->coher_cnt, (flushed) ? "[flushed]" : "",
(encrypt) ? "[encrypt]" : ""));
/* setup header information */
hdr->coher_cnt = (mppe->coher_cnt++) & PIPEX_COHERENCY_CNT_MASK;
hdr->coher_cnt &= PIPEX_COHERENCY_CNT_MASK;
if (flushed)
hdr->coher_cnt |= 0x8000;
if (encrypt)
hdr->coher_cnt |= 0x1000;
hdr->protocol = htons(hdr->protocol);
hdr->coher_cnt = htons(hdr->coher_cnt);
/* encrypt chain */
for (m = m0; m; m = m->m_next) {
cp = mtod(m, u_char *);
len = m->m_len;
if (m == m0 && len > offsetof(struct mppe_header, protocol)) {
len -= offsetof(struct mppe_header, protocol);
cp += offsetof(struct mppe_header, protocol);
}
pipex_mppe_crypt(mppe, len, cp, cp);
}
mtx_leave(&mppe->pxm_mtx);
return (m0);
}
void
pipex_ccp_input(struct mbuf *m0, struct pipex_session *session)
{
u_char *cp;
int code, id, len;
if (m0->m_pkthdr.len < PPP_HDRLEN)
goto drop;
if ((m0 = m_pullup(m0, PPP_HDRLEN)) == NULL)
goto drop;
cp = mtod(m0, u_char *);
GETCHAR(code, cp);
GETCHAR(id, cp);
GETSHORT(len, cp);
switch (code) {
case CCP_RESETREQ:
PIPEX_DBG((session, LOG_DEBUG, "CCP RecvResetReq"));
mtx_enter(&session->mppe_send.pxm_mtx);
session->mppe_send.flags |= PIPEX_MPPE_RESETREQ;
mtx_leave(&session->mppe_send.pxm_mtx);
#ifndef PIPEX_NO_CCP_RESETACK
PIPEX_DBG((session, LOG_DEBUG, "CCP SendResetAck"));
pipex_ccp_output(session, CCP_RESETACK, id);
#endif
/* ignore error */
break;
case CCP_RESETACK:
PIPEX_DBG((session, LOG_DEBUG, "CCP RecvResetAck"));
break;
default:
PIPEX_DBG((session, LOG_DEBUG, "CCP Recv code=%d", code));
goto drop;
}
m_freem(m0);
return;
drop:
m_freem(m0);
counters_inc(session->stat_counters, pxc_ierrors);
}
int
pipex_ccp_output(struct pipex_session *session, int code, int id)
{
u_char *cp;
struct mbuf *m;
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL) {
counters_inc(session->stat_counters, pxc_oerrors);
return (1);
}
m->m_pkthdr.len = m->m_len = 4;
cp = mtod(m, u_char *);
PUTCHAR(code, cp);
PUTCHAR(id, cp);
PUTSHORT(4, cp);
pipex_ppp_output(m, session, PPP_CCP);
return (0);
}
#endif
/***********************************************************************
* Miscellaneous functions
***********************************************************************/
/* adapted from FreeBSD:src/usr.sbin/ppp/tcpmss.c */
/*
* Copyright (c) 2000 Ruslan Ermilov and 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: src/usr.sbin/ppp/tcpmss.c,v 1.1.4.3 2001/07/19 11:39:54 brian Exp $
*/
#define TCP_OPTLEN_IN_SEGMENT 12 /* timestamp option and padding */
#define MAXMSS(mtu) (mtu - sizeof(struct ip) - sizeof(struct tcphdr) - \
TCP_OPTLEN_IN_SEGMENT)
/*
* The following macro is used to update an internet checksum. "acc" is a
* 32-bit accumulation of all the changes to the checksum (adding in old
* 16-bit words and subtracting out new words), and "cksum" is the checksum
* value to be updated.
*/
#define ADJUST_CHECKSUM(acc, cksum) { \
acc += cksum; \
if (acc < 0) { \
acc = -acc; \
acc = (acc >> 16) + (acc & 0xffff); \
acc += acc >> 16; \
cksum = (u_short) ~acc; \
} else { \
acc = (acc >> 16) + (acc & 0xffff); \
acc += acc >> 16; \
cksum = (u_short) acc; \
} \
}
/*
* Rewrite max-segment-size TCP option to avoid PMTU blackhole issues.
* The mtu parameter should be the MTU bottleneck (as far as we know)
* on the link between the source and the destination.
*/
struct mbuf *
adjust_tcp_mss(struct mbuf *m0, int mtu)
{
int opt, optlen, acc, mss, maxmss, lpktp;
struct ip *pip;
struct tcphdr *th;
u_char *pktp, *mssp;
u_int16_t ip_off;
lpktp = sizeof(struct ip) + sizeof(struct tcphdr) + PIPEX_TCP_OPTLEN;
lpktp = MIN(lpktp, m0->m_pkthdr.len);
PIPEX_PULLUP(m0, lpktp);
if (m0 == NULL)
goto drop;
pktp = mtod(m0, char *);
pip = (struct ip *)pktp;
ip_off = ntohs(pip->ip_off);
/* Non TCP or fragmented packet must not have a MSS option */
if (pip->ip_p != IPPROTO_TCP ||
(ip_off & IP_MF) != 0 || (ip_off & IP_OFFMASK) != 0)
goto handled;
pktp += pip->ip_hl << 2;
lpktp -= pip->ip_hl << 2;
/* packet is broken */
if (sizeof(struct tcphdr) > lpktp)
goto drop;
th = (struct tcphdr *)pktp;
/*
* As RFC 973, a MSS field must only be sent in the initial
* connection request(it must be with SYN).
*/
if ((th->th_flags & TH_SYN) == 0)
goto handled;
lpktp = MIN(th->th_off << 4, lpktp);
pktp += sizeof(struct tcphdr);
lpktp -= sizeof(struct tcphdr);
while (lpktp >= TCPOLEN_MAXSEG) {
GETCHAR(opt, pktp);
switch (opt) {
case TCPOPT_MAXSEG:
GETCHAR(optlen, pktp);
mssp = pktp; /* mss place holder */
GETSHORT(mss, pktp);
maxmss = MAXMSS(mtu);
if (mss > maxmss) {
PIPEX_DBG((NULL, LOG_DEBUG,
"change tcp-mss %d => %d", mss, maxmss));
PUTSHORT(maxmss, mssp);
acc = htons(mss);
acc -= htons(maxmss);
ADJUST_CHECKSUM(acc, th->th_sum);
}
goto handled;
/* NOTREACHED */
case TCPOPT_EOL:
goto handled;
/* NOTREACHED */
case TCPOPT_NOP:
lpktp--;
break;
default:
GETCHAR(optlen, pktp);
if (optlen < 2) /* packet is broken */
goto drop;
pktp += optlen - 2;
lpktp -= optlen;
break;
}
}
handled:
return (m0);
drop:
m_freem(m0);
return (NULL);
}
/*
* Check whether a packet should reset idle timer
* Returns 1 to don't reset timer (i.e. the packet is "idle" packet)
*/
struct mbuf *
ip_is_idle_packet(struct mbuf *m0, int *ris_idle)
{
u_int16_t ip_off;
const struct udphdr *uh;
struct ip *pip;
int len;
/* pullup ip header */
len = sizeof(struct ip);
PIPEX_PULLUP(m0, len);
if (m0 == NULL)
goto error;
pip = mtod(m0, struct ip *);
/*
* the packet which fragmentations was not the idle packet.
*/
ip_off = ntohs(pip->ip_off);
if ((ip_off & IP_MF) || ((ip_off & IP_OFFMASK) != 0))
goto is_active;
switch (pip->ip_p) {
case IPPROTO_IGMP:
goto is_active;
case IPPROTO_ICMP:
len = pip->ip_hl * 4 + 8;
PIPEX_PULLUP(m0, len);
if (m0 == NULL)
goto error;
pip = mtod(m0, struct ip *);
switch (((unsigned char *) pip)[pip->ip_hl * 4]) {
case 0: /* Echo Reply */
case 8: /* Echo Request */
goto is_active;
default:
goto is_idle;
}
case IPPROTO_UDP:
case IPPROTO_TCP:
len = pip->ip_hl * 4 + sizeof(struct udphdr);
PIPEX_PULLUP(m0, len);
if (m0 == NULL)
goto error;
pip = mtod(m0, struct ip *);
uh = (struct udphdr *)(mtod(m0, caddr_t) + pip->ip_hl * 4);
switch (ntohs(uh->uh_sport)) {
case 53: /* DOMAIN */
case 67: /* BOOTPS */
case 68: /* BOOTPC */
case 123: /* NTP */
case 137: /* NETBIOS-NS */
case 520: /* RIP */
goto is_idle;
}
switch (ntohs(uh->uh_dport)) {
case 53: /* DOMAIN */
case 67: /* BOOTPS */
case 68: /* BOOTPC */
case 123: /* NTP */
case 137: /* NETBIOS-NS */
case 520: /* RIP */
goto is_idle;
}
goto is_active;
default:
goto is_active;
}
is_active:
*ris_idle = 0;
return (m0);
is_idle:
*ris_idle = 1;
return (m0);
error:
return (NULL);
}
void
pipex_session_log(struct pipex_session *session, int prio, const char *fmt, ...)
{
char logbuf[1024];
va_list ap;
logpri(prio);
if (session != NULL) {
struct ifnet *ifp;
ifp = if_get(session->ifindex);
addlog("pipex: ppp=%d iface=%s protocol=%s id=%d ",
session->ppp_id,
ifp? ifp->if_xname : "Unknown",
(session->protocol == PIPEX_PROTO_PPPOE)? "PPPoE" :
(session->protocol == PIPEX_PROTO_PPTP)? "PPTP" :
(session->protocol == PIPEX_PROTO_L2TP) ? "L2TP" :
"Unknown", session->session_id);
if_put(ifp);
} else
addlog("pipex: ");
va_start(ap, fmt);
vsnprintf(logbuf, sizeof(logbuf), fmt, ap);
va_end(ap);
addlog("%s\n", logbuf);
}
uint32_t
pipex_sockaddr_hash_key(struct sockaddr *sa)
{
switch (sa->sa_family) {
case AF_INET:
return ntohl(satosin(sa)->sin_addr.s_addr);
case AF_INET6:
return ntohl(satosin6(sa)->sin6_addr.s6_addr32[3]);
}
panic("pipex_sockaddr_hash_key: unknown address family");
return (0);
}
/*
* Compare struct sockaddr_in{,6} with the address only.
* The port number is not covered.
*/
int
pipex_sockaddr_compar_addr(struct sockaddr *a, struct sockaddr *b)
{
int cmp;
cmp = b->sa_family - a->sa_family;
if (cmp != 0)
return cmp;
switch (a->sa_family) {
case AF_INET:
return (satosin(b)->sin_addr.s_addr -
satosin(a)->sin_addr.s_addr);
case AF_INET6:
cmp = (satosin6(b)->sin6_scope_id - satosin6(a)->sin6_scope_id);
if (cmp != 0)
return cmp;
return (memcmp(&satosin6(a)->sin6_addr,
&satosin6(b)->sin6_addr,
sizeof(struct in6_addr)));
}
panic("pipex_sockaddr_compar_addr: unknown address family");
return (-1);
}
int
pipex_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp,
size_t newlen)
{
switch (name[0]) {
case PIPEXCTL_ENABLE:
if (namelen != 1)
return (ENOTDIR);
return (sysctl_int_bounded(oldp, oldlenp, newp, newlen,
&pipex_enable, 0, 1));
default:
return (ENOPROTOOPT);
}
/* NOTREACHED */
}