summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorYASUOKA Masahiko <yasuoka@cvs.openbsd.org>2010-01-11 03:50:57 +0000
committerYASUOKA Masahiko <yasuoka@cvs.openbsd.org>2010-01-11 03:50:57 +0000
commit07b6c47bf3506b1d17f7c5d0a4996c64403ffe9d (patch)
treeea4224e3fe2f8a4cbf4fc9276d2aee2660988032 /sys
parent944b7335f573c141a17ea60164543855f70a2e2e (diff)
Initial import PIPEX. PIPEX(Pppac IP EXtension) is a IP forwarding
acceleration for PPP access concentrator. ok mcbride@ dlg@ deraadt@ reyk@.
Diffstat (limited to 'sys')
-rw-r--r--sys/conf/files3
-rw-r--r--sys/net/if_ethersubr.c19
-rw-r--r--sys/net/if_tun.c38
-rw-r--r--sys/net/pipex.c2478
-rw-r--r--sys/net/pipex.h166
-rw-r--r--sys/net/pipex_local.h319
-rw-r--r--sys/sys/mbuf.h3
7 files changed, 3020 insertions, 6 deletions
diff --git a/sys/conf/files b/sys/conf/files
index ec7b62068c5..80a66d8cc11 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1,4 +1,4 @@
-# $OpenBSD: files,v 1.483 2009/11/25 19:07:52 oga Exp $
+# $OpenBSD: files,v 1.484 2010/01/11 03:50:55 yasuoka Exp $
# $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $
# @(#)files.newconf 7.5 (Berkeley) 5/10/93
@@ -811,6 +811,7 @@ file net/if_tun.c tun needs-count
file net/if_bridge.c bridge needs-count
file net/bridgestp.c bridge
file net/if_vlan.c vlan needs-count
+file net/pipex.c pipex
file net/radix.c
file net/radix_mpath.c !small_kernel
file net/raw_cb.c
diff --git a/sys/net/if_ethersubr.c b/sys/net/if_ethersubr.c
index 1cdb60d0287..595355fba0d 100644
--- a/sys/net/if_ethersubr.c
+++ b/sys/net/if_ethersubr.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_ethersubr.c,v 1.136 2009/11/03 10:59:04 claudio Exp $ */
+/* $OpenBSD: if_ethersubr.c,v 1.137 2010/01/11 03:50:56 yasuoka Exp $ */
/* $NetBSD: if_ethersubr.c,v 1.19 1996/05/07 02:40:30 thorpej Exp $ */
/*
@@ -147,6 +147,10 @@ didn't get a copy, you may request one from <license@ipv6.nrl.navy.mil>.
#include <netinet6/nd6.h>
#endif
+#ifdef PIPEX
+#include <net/pipex.h>
+#endif
+
#ifdef NETATALK
#include <netatalk/at.h>
#include <netatalk/at_var.h>
@@ -736,7 +740,7 @@ decapsulate:
aarpinput((struct arpcom *)ifp, m);
goto done;
#endif
-#if NPPPOE > 0
+#if NPPPOE > 0 || defined(PIPEX)
case ETHERTYPE_PPPOEDISC:
case ETHERTYPE_PPPOE:
/* XXX we dont have this flag */
@@ -758,7 +762,16 @@ decapsulate:
eh_tmp = mtod(m, struct ether_header *);
bcopy(eh, eh_tmp, sizeof(struct ether_header));
+#ifdef PIPEX
+ {
+ struct pipex_session *session;
+ if ((session = pipex_pppoe_lookup_session(m)) != NULL) {
+ pipex_pppoe_input(m, session);
+ return;
+ }
+ }
+#endif
if (etype == ETHERTYPE_PPPOEDISC)
inq = &pppoediscinq;
else
@@ -766,7 +779,7 @@ decapsulate:
schednetisr(NETISR_PPPOE);
break;
-#endif /* NPPPOE > 0 */
+#endif /* NPPPOE > 0 || defined(PIPEX) */
#ifdef AOE
case ETHERTYPE_AOE:
aoe_input(ifp, m);
diff --git a/sys/net/if_tun.c b/sys/net/if_tun.c
index 3debfa9b667..b65193ce98c 100644
--- a/sys/net/if_tun.c
+++ b/sys/net/if_tun.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: if_tun.c,v 1.100 2009/11/09 17:53:39 nicm Exp $ */
+/* $OpenBSD: if_tun.c,v 1.101 2010/01/11 03:50:56 yasuoka Exp $ */
/* $NetBSD: if_tun.c,v 1.24 1996/05/07 02:40:48 thorpej Exp $ */
/*
@@ -72,6 +72,10 @@
#include <netinet/if_ether.h>
#endif
+#ifdef PIPEX
+#include <net/pipex.h>
+#endif
+
#ifdef NETATALK
#include <netatalk/at.h>
#include <netatalk/at_var.h>
@@ -99,6 +103,10 @@ struct tun_softc {
pid_t tun_pgid; /* the process group - if any */
u_short tun_flags; /* misc flags */
#define tun_if arpcom.ac_if
+#ifdef PIPEX
+ /* pipex context */
+ struct pipex_iface_context pipex_iface;
+#endif
};
#ifdef TUN_DEBUG
@@ -155,6 +163,9 @@ tunattach(int n)
{
LIST_INIT(&tun_softc_list);
if_clone_attach(&tun_cloner);
+#ifdef PIPEX
+ pipex_init();
+#endif
}
int
@@ -231,6 +242,9 @@ tun_create(struct if_clone *ifc, int unit, int flags)
s = splnet();
LIST_INSERT_HEAD(&tun_softc_list, tp, tun_list);
splx(s);
+#ifdef PIPEX
+ pipex_iface_init(&tp->pipex_iface, ifp);
+#endif
return (0);
}
@@ -241,6 +255,9 @@ tun_clone_destroy(struct ifnet *ifp)
struct tun_softc *tp = ifp->if_softc;
int s;
+#ifdef PIPEX
+ pipex_iface_stop(&tp->pipex_iface);
+#endif
tun_wakeup(tp);
s = splhigh();
@@ -583,6 +600,9 @@ tun_output(struct ifnet *ifp, struct mbuf *m0, struct sockaddr *dst,
struct tun_softc *tp = ifp->if_softc;
int s, len, error;
u_int32_t *af;
+#ifdef PIPEX
+ struct pipex_session *session;
+#endif /* PIPEX */
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
m_freem(m0);
@@ -614,6 +634,13 @@ tun_output(struct ifnet *ifp, struct mbuf *m0, struct sockaddr *dst,
if (ifp->if_bpf)
bpf_mtap(ifp->if_bpf, m0, BPF_DIRECTION_OUT);
#endif
+#ifdef PIPEX
+ if ((session = pipex_ip_lookup_session(m0, &tp->pipex_iface)) != NULL) {
+ pipex_ip_output(m0, session);
+ simple_unlock(&tp->pppac_lock);
+ return (0);
+ }
+#endif /* PIPEX */
len = m0->m_pkthdr.len;
IFQ_ENQUEUE(&ifp->if_snd, m0, NULL, error);
@@ -746,8 +773,17 @@ tunioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
sizeof(tp->arpcom.ac_enaddr));
break;
default:
+#ifdef PIPEX
+ {
+ int ret;
+ ret = pipex_ioctl(&tp->pipex_iface, cmd, data);
+ splx(s);
+ return ret;
+ }
+#else
splx(s);
return (ENOTTY);
+#endif
}
splx(s);
return (0);
diff --git a/sys/net/pipex.c b/sys/net/pipex.c
new file mode 100644
index 00000000000..adac198a610
--- /dev/null
+++ b/sys/net/pipex.c
@@ -0,0 +1,2478 @@
+/* $Id: pipex.c,v 1.1 2010/01/11 03:50:56 yasuoka 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.
+ */
+/*
+ * PIPEX(PPPAC IP Extension)
+ */
+/*
+ * FIXME: don't use != 1 for boolean like variable names.
+ * FIXME: we don't use sysctl now, delete sysctl comments.
+ */
+#ifdef _KERNEL_OPT
+#include "opt_inet.h"
+#include "opt_pipex.h"
+#endif
+
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/syslog.h>
+#include <sys/conf.h>
+#include <sys/time.h>
+#if defined(__OpenBSD__)
+#include <sys/timeout.h>
+#endif
+#include <sys/kernel.h>
+
+#include <net/if.h>
+
+#if defined(__NetBSD__)
+#include <machine/stdarg.h>
+#include <net/if_ether.h>
+#else
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#endif
+
+#include <net/radix.h>
+#include <net/route.h>
+#include <net/netisr.h>
+#include <net/ppp_defs.h>
+#include <net/ppp-comp.h>
+
+#include "bpfilter.h"
+#if NBPFILTER > 0
+#include <sys/time.h>
+#include <net/bpf.h>
+#endif
+
+#ifdef INET
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#endif
+
+#include <net/pipex.h>
+#include "pipex_local.h"
+
+#ifdef PIPEX_MPPE
+#ifdef NBSAFE_RC4
+#include <lib/libnbsafe/nbsafe.h>
+#else
+#if defined(__NetBSD__)
+#include <crypto/arc4/arc4.h>
+#else
+#include <crypto/arc4.h>
+#endif
+#endif
+#endif
+/*
+ * static/global variables
+ */
+static LIST_HEAD(pipex_hash_head, pipex_session)
+ pipex_session_list, /* master session list */
+ pipex_close_wait_list, /* expired session list */
+ pipex_peer_addr_hashtable[PIPEX_HASH_SIZE], /* peer's address hash */
+ pipex_id_hashtable[PIPEX_HASH_SIZE]; /* peer id hash */
+static struct radix_node_head *pipex_rd_head4 = NULL;
+static struct timeout pipex_timer_ch; /* callout timer context */
+static int pipex_prune = 1; /* walk list every seconds */
+
+/* pipex traffic queue */
+struct ifqueue pipexinq = { NULL };
+struct ifqueue pipexoutq = { NULL };
+struct pipex_tag {
+ struct pipex_session *session;
+};
+#if defined(__OpenBSD__) || defined(__HAVE_GENERIC_SOFT_INTERRUPTS)
+void *pipex_softintr = NULL;
+static void pipex_softintr_handler(void *);
+#else
+struct callout pipex_softintr = CALLOUT_INITIALIZER;
+void pipex_softintr_handler(void *);
+#endif
+
+#ifdef PIPEX_DEBUG
+int pipex_debug = 0; /* 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
+
+/* see the comment on pipex_mppe_input() */
+#define WORKAROUND_OUT_OF_SEQUENCE_PPP_FRAMING 1
+
+/************************************************************************
+ * Core functions
+ ************************************************************************/
+/*
+ * pipex_init - first initialize of pipex.
+ */
+void
+pipex_init(void)
+{
+ extern int max_keylen; /* for radix.c */
+
+ LIST_INIT(&pipex_session_list);
+
+ if (sizeof(struct sockaddr_in) > max_keylen)
+ max_keylen = sizeof(struct sockaddr_in);
+ memset(pipex_id_hashtable, 0, sizeof(pipex_id_hashtable));
+ memset(pipex_peer_addr_hashtable, 0, sizeof(pipex_peer_addr_hashtable));
+ /* queue and softintr init */
+ IFQ_SET_MAXLEN(&pipexinq, IFQ_MAXLEN);
+ IFQ_SET_MAXLEN(&pipexoutq, IFQ_MAXLEN);
+#if defined(__OpenBSD__) || defined(__HAVE_GENERIC_SOFT_INTERRUPTS)
+ pipex_softintr =
+ softintr_establish(IPL_SOFTNET, pipex_softintr_handler, NULL);
+#endif
+}
+
+/*
+ * pipex_iface_init - interface pipex initiilze.
+ */
+void
+pipex_iface_init(struct pipex_iface_context *pipex_iface, struct ifnet *ifp)
+{
+ int s;
+ struct pipex_session *session;
+ void *ptr;
+
+ pipex_iface->pipexmode = PIPEX_DISABLE;
+ pipex_iface->ifnet_this = ifp;
+
+ s = splnet();
+ if (pipex_rd_head4 == NULL) {
+ /* XXX avoid dereferencing pointer warning */
+ ptr = pipex_rd_head4;
+
+ rn_inithead(&ptr, offsetof(struct sockaddr_in, sin_addr) *
+ NBBY);
+ pipex_rd_head4 = ptr;
+ }
+ splx(s);
+
+ /* virtual pipex_session entry for multicast */
+ session = malloc(sizeof(*session), M_TEMP, M_WAITOK);
+ session->is_multicast = 1;
+ session->pipex_iface = pipex_iface;
+ pipex_iface->multicast_session = session;
+}
+
+/*
+ * pipex_start - start interface pipex.
+ */
+void
+pipex_iface_start(struct pipex_iface_context *pipex_iface)
+{
+ pipex_iface->pipexmode |= PIPEX_ENABLED;
+}
+
+/*
+ * pipex_stop - clear interface pipex sessions.
+ */
+void
+pipex_iface_stop(struct pipex_iface_context *pipex_iface)
+{
+ struct pipex_session *session;
+ struct pipex_session *session_next;
+ int s;
+
+ s = splnet();
+ pipex_iface->pipexmode = PIPEX_DISABLE;
+ /*
+ * traversal all pipex sessions.
+ * it will become heavy if the number of pppac devices bocomes large.
+ */
+ for (session = LIST_FIRST(&pipex_session_list);
+ session; session = session_next) {
+ session_next = LIST_NEXT(session, session_list);
+ if (session->pipex_iface == pipex_iface)
+ pipex_destroy_session(session);
+ }
+ splx(s);
+
+ return;
+}
+
+/*
+ * pppac_ioctl. Please call with the mutex of outer object of 'pipex_iface'.
+ */
+int
+pipex_ioctl(struct pipex_iface_context *pipex_iface, int cmd, caddr_t data)
+{
+ int mode, ret;
+
+ switch (cmd) {
+ case PIPEXSMODE:
+ mode = *(int *)data;
+ if (pipex_iface->pipexmode != mode) {
+ if (mode == PIPEX_ENABLE)
+ pipex_iface_start(pipex_iface);
+ else
+ pipex_iface_stop(pipex_iface);
+ }
+ break;
+
+ case PIPEXGMODE:
+ *(int *)data = pipex_iface->pipexmode;
+ break;
+
+ case PIPEXASESSION:
+ ret = pipex_add_session((struct pipex_session_req *)data,
+ pipex_iface);
+ return ret;
+
+ case PIPEXDSESSION:
+ ret = pipex_close_session(
+ (struct pipex_session_close_req *)data);
+ return ret;
+
+ case PIPEXCSESSION:
+ ret = pipex_config_session(
+ (struct pipex_session_config_req *)data);
+ return ret;
+
+ case PIPEXGSTAT:
+ ret = pipex_get_stat((struct pipex_session_stat_req *)data);
+ return ret;
+
+ case PIPEXGCLOSED:
+ ret = pipex_get_closed((struct pipex_session_list_req *)data);
+ return (ret);
+
+ default:
+ return ENOTTY;
+
+ }
+ return 0;
+}
+
+/************************************************************************
+ * Session management functions
+ ************************************************************************/
+static int
+pipex_add_session(struct pipex_session_req *req,
+ struct pipex_iface_context *iface)
+{
+ struct pipex_session *session;
+ struct pipex_hash_head *chain;
+ struct radix_node *rn;
+ int s;
+#ifdef PIPEX_PPPOE
+ struct ifnet *over_ifp = NULL;
+ struct ether_header *eh;
+ struct sockaddr peer_addr;
+#endif
+
+ /* Checks requeted parameters. */
+ if (iface->pipexmode != PIPEX_ENABLE)
+ return ENXIO;
+ switch (req->pr_protocol) {
+#ifdef PIPEX_PPPOE
+ case PIPEX_PROTO_PPPOE:
+ over_ifp = ifunit(req->pr_proto.pppoe.over_ifname);
+ if (over_ifp == NULL)
+ return EINVAL;
+ bzero(&peer_addr, sizeof(peer_addr));
+ peer_addr.sa_family = AF_UNSPEC;
+ eh = (struct ether_header*)&peer_addr.sa_data;
+ eh->ether_type = htons(ETHERTYPE_PPPOE);
+ memcpy(&eh->ether_dhost, &req->pr_proto.pppoe.peer_address,
+ sizeof(eh->ether_dhost));
+ break;
+#endif
+#ifdef PIPEX_PPTP
+ case PIPEX_PROTO_PPTP:
+ break;
+#endif
+ default:
+ return EPROTONOSUPPORT;
+ }
+
+ /* prepare a new session */
+ session = malloc(sizeof(*session), M_TEMP, M_WAITOK | M_ZERO);
+ if (session == NULL)
+ return ENOMEM;
+
+ session->state = PIPEX_STATE_OPENED;
+ 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->pipex_iface = iface;
+ session->ppp_flags = req->pr_ppp_flags;
+ session->ppp_id = req->pr_ppp_id;
+
+ session->ip_forward = 1;
+
+ 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;
+
+#ifdef PIPEX_PPPOE
+ if (req->pr_protocol == PIPEX_PROTO_PPPOE) {
+ session->proto.pppoe.peer_addr = peer_addr;
+ session->proto.pppoe.over_ifp = over_ifp;
+ }
+#endif
+#ifdef PIPEX_PPTP
+ if (req->pr_protocol == PIPEX_PROTO_PPTP) {
+ session->proto.pptp.snd_gap = 0;
+ session->proto.pptp.rcv_gap = 0;
+ session->proto.pptp.snd_una = req->pr_proto.pptp.snd_una;
+ session->proto.pptp.snd_nxt = req->pr_proto.pptp.snd_nxt;
+ session->proto.pptp.rcv_nxt = req->pr_proto.pptp.rcv_nxt;
+ session->proto.pptp.rcv_acked = req->pr_proto.pptp.rcv_acked;
+ session->proto.pptp.winsz = req->pr_proto.pptp.winsz;
+ session->proto.pptp.maxwinsz = req->pr_proto.pptp.maxwinsz;
+ session->proto.pptp.peer_maxwinsz
+ = req->pr_proto.pptp.peer_maxwinsz;
+ session->proto.pptp.peer_address =
+ req->pr_proto.pptp.peer_address;
+ session->proto.pptp.our_address =
+ req->pr_proto.pptp.our_address;
+ }
+#endif
+#ifdef PIPEX_MPPE
+#if 1
+#define arc4_ctxlen() (sizeof(struct rc4_ctx))
+#endif
+ KASSERT(arc4_ctxlen() <= PIPEX_RC4_CTXLEN);
+
+ if ((req->pr_ppp_flags & PIPEX_PPP_MPPE_ACCEPTED) != 0)
+ pipex_mppe_req_init(&req->pr_mppe_recv, &session->mppe_recv);
+ if ((req->pr_ppp_flags & PIPEX_PPP_MPPE_ENABLED) != 0)
+ pipex_mppe_req_init(&req->pr_mppe_send, &session->mppe_send);
+
+ if (pipex_session_is_mppe_required(session)) {
+ if (!pipex_session_is_mppe_enabled(session) ||
+ !pipex_session_is_mppe_accepted(session)) {
+ free(session, M_TEMP);
+ return EINVAL;
+ }
+ }
+#endif
+
+ /* commit the session */
+ s = splnet();
+ if (pipex_lookup_by_ip_address(session->ip_address.sin_addr) != NULL) {
+ splx(s);
+ free(session, M_TEMP);
+ return EADDRINUSE;
+ }
+
+ rn = pipex_rd_head4->rnh_addaddr(&session->ip_address,
+ &session->ip_netmask, pipex_rd_head4, session->ps4_rn, RTP_STATIC);
+ KASSERT(rn != NULL);
+ if (rn == NULL) {
+ splx(s);
+ free(session, M_TEMP);
+ return ENOMEM;
+ }
+
+ chain = PIPEX_ID_HASHTABLE(session->session_id);
+ LIST_INSERT_HEAD(chain, session, id_chain);
+ LIST_INSERT_HEAD(&pipex_session_list, session, session_list);
+#ifdef PIPEX_PPTP
+ if (req->pr_protocol == PIPEX_PROTO_PPTP) {
+ chain = PIPEX_PEER_ADDR_HASHTABLE(
+ session->proto.pptp.peer_address.s_addr);
+ 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();
+
+ splx(s);
+
+ pipex_session_log(session, LOG_INFO, "PIPEX is ready.");
+
+ return 0;
+}
+
+int
+pipex_notify_close_session(struct pipex_session *session)
+{
+ int s;
+
+ s = splnet();
+ session->state = PIPEX_STATE_CLOSE_WAIT;
+ session->stat.idle_time = 0;
+ LIST_INSERT_HEAD(&pipex_close_wait_list, session, state_list);
+ splx(s);
+
+ return 0;
+}
+
+int
+pipex_notify_close_session_all(void)
+{
+ struct pipex_session *session;
+ int s;
+
+ s = splnet();
+ LIST_FOREACH(session, &pipex_session_list, session_list) {
+ if (session->state == PIPEX_STATE_OPENED)
+ pipex_notify_close_session(session);
+ }
+ splx(s);
+
+ return 0;
+}
+
+static int
+pipex_close_session(struct pipex_session_close_req *req)
+{
+ struct pipex_session *session;
+ int s;
+
+ s = splnet();
+ session = pipex_lookup_by_session_id(req->pcr_protocol,
+ req->pcr_session_id);
+ if (session == NULL) {
+ splx(s);
+ return EINVAL;
+ }
+
+ /* remove from close_wait list */
+ if (session->state == PIPEX_STATE_CLOSE_WAIT)
+ LIST_REMOVE((struct pipex_session *)session, state_list);
+
+ /* get statistics before destroy the session */
+ req->pcr_stat = session->stat;
+ session->state = PIPEX_STATE_CLOSED;
+ splx(s);
+
+ return 0;
+}
+
+static int
+pipex_config_session(struct pipex_session_config_req *req)
+{
+ struct pipex_session *session;
+ int s;
+
+ s = splnet();
+ session = pipex_lookup_by_session_id(req->pcr_protocol,
+ req->pcr_session_id);
+ if (session == NULL) {
+ splx(s);
+ return EINVAL;
+ }
+ session->ip_forward = req->pcr_ip_forward;
+ splx(s);
+
+ return 0;
+}
+
+static int
+pipex_get_stat(struct pipex_session_stat_req *req)
+{
+ struct pipex_session *session;
+ int s;
+
+ s = splnet();
+ session = pipex_lookup_by_session_id(req->psr_protocol,
+ req->psr_session_id);
+ if (session == NULL) {
+ splx(s);
+ return EINVAL;
+ }
+ req->psr_stat = session->stat;
+ splx(s);
+
+ return 0;
+}
+
+static int
+pipex_get_closed(struct pipex_session_list_req *req)
+{
+ struct pipex_session *session;
+ int s;
+
+ s = splnet();
+ bzero((u_char *)req, sizeof(struct pipex_session_list_req));
+ while (!LIST_EMPTY(&pipex_close_wait_list)) {
+ session = LIST_FIRST(&pipex_close_wait_list);
+ req->plr_ppp_id[req->plr_ppp_id_count++] = session->ppp_id;
+ LIST_REMOVE((struct pipex_session *)session, state_list);
+ if (req->plr_ppp_id_count >= PIPEX_MAX_LISTREQ) {
+ if (LIST_NEXT(session, state_list))
+ req->plr_flags |= PIPEX_LISTREQ_MORE;
+ break;
+ }
+ }
+ splx(s);
+
+ return 0;
+}
+
+static int
+pipex_destroy_session(struct pipex_session *session)
+{
+ struct radix_node *rn;
+ int s;
+
+ /* remove from radix tree and hash chain */
+ s = splnet();
+ rn = NULL;
+ rn = pipex_rd_head4->rnh_deladdr(&session->ip_address,
+ &session->ip_netmask, pipex_rd_head4, (struct radix_node *)session);
+ KASSERT(rn != NULL);
+
+ LIST_REMOVE((struct pipex_session *)session, id_chain);
+ LIST_REMOVE((struct pipex_session *)session, session_list);
+#ifdef PIPEX_PPTP
+ if (session->protocol == PIPEX_PROTO_PPTP) {
+ LIST_REMOVE((struct pipex_session *)session,
+ peer_addr_chain);
+ }
+#endif
+ /* if final session is destroyed, stop timer */
+ if (LIST_EMPTY(&pipex_session_list))
+ pipex_timer_stop();
+
+ splx(s);
+
+ free(session, M_TEMP);
+
+ return 0;
+}
+
+static struct pipex_session *
+pipex_lookup_by_ip_address(struct in_addr addr)
+{
+ struct pipex_session *session;
+ struct sockaddr_in pipex_in4 = {
+ .sin_family = AF_INET,
+ .sin_len = sizeof(struct sockaddr_in),
+ }, pipex_in4mask = {
+ .sin_family = AF_INET,
+ .sin_len = sizeof(struct sockaddr_in),
+ .sin_addr = { .s_addr = htonl(0xFFFFFFFFL) }
+ };
+
+ pipex_in4.sin_addr = addr;
+
+ session = (struct pipex_session *)pipex_rd_head4->rnh_lookup(
+ &pipex_in4, &pipex_in4mask, pipex_rd_head4);
+
+#ifdef PIPEX_DEBUG
+ if (session == NULL)
+ PIPEX_DBG((NULL, LOG_DEBUG, "<%s> session not found (addr=%s)",
+ __func__, inet_ntoa(addr)));
+#endif
+
+ return session;
+}
+
+static struct pipex_session *
+pipex_lookup_by_session_id(int protocol, int session_id)
+{
+ struct pipex_hash_head *list;
+ struct pipex_session *session;
+
+ 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;
+}
+
+/***********************************************************************
+ * Queue and Software Interrupt Handler
+ ***********************************************************************/
+#if defined(__OpenBSD__) || defined(__HAVE_GENERIC_SOFT_INTERRUPTS)
+static void
+pipex_softintr_handler(void *dummy)
+{
+ /* called at splsoftnet() */
+ pipex_ppp_dequeue();
+}
+#else
+void
+pipex_softintr_handler(void *dummy)
+{
+ int s;
+
+ s = splsoftnet();
+ pipex_ppp_dequeue();
+ callout_deactivate(&pipex_softintr);
+ splx(s);
+}
+#endif
+
+static void
+pipex_ppp_dequeue(void)
+{
+ struct mbuf *m;
+ struct m_tag *mtag;
+ struct pipex_tag *tag;
+ int c, s;
+
+ /* ppp output */
+ for (c = 0; c < PIPEX_DEQUEUE_LIMIT; c++) {
+ s = splnet();
+ IF_DEQUEUE(&pipexoutq, m);
+ if (m == NULL) {
+ splx(s);
+ break;
+ }
+ splx(s);
+
+ mtag = m_tag_find(m, PACKET_TAG_PIPEX, NULL);
+ KASSERT(mtag != NULL);
+ if (mtag == NULL) {
+ m_freem(m);
+ continue;
+ }
+ tag = (struct pipex_tag *)(mtag + 1);
+ if (tag->session->is_multicast != 0) {
+ struct pipex_session *session;
+ struct mbuf *m0;
+
+ LIST_FOREACH(session, &pipex_session_list,
+ session_list) {
+ if (session->pipex_iface !=
+ tag->session->pipex_iface)
+ continue;
+ if (session->ip_forward == 0)
+ continue;
+ m0 = m_copym(m, 0, M_COPYALL, M_WAITOK);
+ if (m0 == NULL) {
+ session->stat.oerrors++;
+ continue;
+ }
+ pipex_ppp_output(m0, session, PPP_IP);
+ }
+ m_freem(m);
+ } else
+ pipex_ppp_output(m, tag->session, PPP_IP);
+ }
+
+ /* ppp input */
+ for (c = 0; c < PIPEX_DEQUEUE_LIMIT; c++) {
+ s = splnet();
+ IF_DEQUEUE(&pipexinq, m);
+ if (m == NULL) {
+ splx(s);
+ break;
+ }
+ splx(s);
+
+ mtag = m_tag_find(m, PACKET_TAG_PIPEX, NULL);
+ KASSERT(mtag != NULL);
+ if (mtag == NULL) {
+ m_freem(m);
+ continue;
+ }
+ tag = (struct pipex_tag *)(mtag + 1);
+ pipex_ppp_input(m, tag->session, 0);
+ }
+
+ /*
+ * When packet remains in queue, it is necessary
+ * to re-schedule software interrupt.
+ */
+ s = splnet();
+ if (!IF_IS_EMPTY(&pipexinq) || !IF_IS_EMPTY(&pipexoutq)) {
+#if defined(__OpenBSD__) || defined(__HAVE_GENERIC_SOFT_INTERRUPTS)
+ softintr_schedule(pipex_softintr);
+#else
+ if (!callout_active(&pipex_softintr))
+ callout_reset(&pipex_softintr, 1,
+ pipex_softintr_handler, NULL);
+#endif
+ }
+ splx(s);
+
+ return;
+}
+
+static int
+pipex_ppp_enqueue(struct mbuf *m0, struct pipex_session *session,
+ struct ifqueue *queue)
+{
+ struct pipex_tag *tag;
+ struct m_tag *mtag;
+ int s;
+
+ s = splnet();
+ if (IF_QFULL(queue)) {
+ IF_DROP(queue);
+ splx(s);
+ goto fail;
+ }
+ mtag = m_tag_get(PACKET_TAG_PIPEX, sizeof(struct pipex_tag), M_NOWAIT);
+ if (mtag == NULL) {
+ splx(s);
+ goto fail;
+ }
+ m_tag_prepend(m0, mtag);
+ tag = (struct pipex_tag *)(mtag + 1);
+ tag->session = session;
+
+ IF_ENQUEUE(queue, m0);
+ splx(s);
+
+#if defined(__OpenBSD__) || defined(__HAVE_GENERIC_SOFT_INTERRUPTS)
+ softintr_schedule(pipex_softintr);
+#else
+ if (!callout_active(&pipex_softintr))
+ callout_reset(&pipex_softintr, 1, pipex_softintr_handler,
+ NULL);
+#endif
+ return 0;
+
+fail:
+ /*
+ * if pipex_ppp_enqueue is failed,
+ * it is necessary to free mbuf.
+ */
+ return 1;
+}
+
+/***********************************************************************
+ * Timer functions
+ ***********************************************************************/
+/*
+ * pipex_timer_start
+ */
+static void
+pipex_timer_start(void)
+{
+ /* init timeout */
+ timeout_set(&pipex_timer_ch, pipex_timer, NULL);
+ timeout_add_sec(&pipex_timer_ch, pipex_prune);
+}
+
+/*
+ * pipex_timer_stop
+ */
+static void
+pipex_timer_stop(void)
+{
+ timeout_del(&pipex_timer_ch);
+}
+
+/*
+ * pipex_timer
+ */
+static void
+pipex_timer(void *ignored_arg)
+{
+ int s;
+ struct pipex_session *session;
+ struct pipex_session *session_next;
+
+ s = splnet();
+ timeout_add_sec(&pipex_timer_ch, pipex_prune);
+
+ /* walk through */
+ for (session = LIST_FIRST(&pipex_session_list); session;
+ session = session_next) {
+ session_next = LIST_NEXT(session, session_list);
+ switch (session->state) {
+ case PIPEX_STATE_OPENED:
+ if (session->timeout_sec == 0)
+ continue;
+
+ session->stat.idle_time++;
+ if (session->stat.idle_time < session->timeout_sec)
+ continue;
+
+ pipex_notify_close_session(session);
+ break;
+
+ case PIPEX_STATE_CLOSE_WAIT:
+ session->stat.idle_time++;
+ if (session->stat.idle_time < PIPEX_CLOSE_TIMEOUT)
+ continue;
+ session->state = PIPEX_STATE_CLOSED;
+ /* FALLTHROUGH */
+ case PIPEX_STATE_CLOSED:
+ /*
+ * if mbuf which queued pipexinq has
+ * session reference pointer, the
+ * referenced session must not destroy.
+ */
+ if (!IF_IS_EMPTY(&pipexinq) ||
+ !IF_IS_EMPTY(&pipexoutq))
+ continue;
+
+ pipex_destroy_session(session);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ splx(s);
+}
+
+/***********************************************************************
+ * Common network I/O functions. (tunnel protocol independent)
+ ***********************************************************************/
+/*
+ * pipex_ip_lookup_session - assuming that the IP packet was inputted.
+ */
+struct pipex_session *
+pipex_ip_lookup_session(struct mbuf *m0, struct pipex_iface_context
+ *pipex_iface)
+{
+ struct pipex_session *session;
+ struct ip ip;
+
+ /* length check */
+ if (m0->m_pkthdr.len < sizeof(struct ip)) {
+ PIPEX_DBG((NULL, LOG_DEBUG,
+ "<%s> packet length is too short", __func__));
+ return NULL;
+ }
+
+ /* copy ip header info */
+ m_copydata(m0, 0, sizeof(struct ip), (caddr_t)&ip);
+
+ if (IN_MULTICAST(ip.ip_dst.s_addr) && pipex_iface != NULL)
+ return pipex_iface->multicast_session;
+
+ /* lookup pipex session table */
+ session = pipex_lookup_by_ip_address(ip.ip_dst);
+ if (session == NULL) {
+ PIPEX_DBG((NULL, LOG_DEBUG, "<%s> session not found.",
+ __func__));
+ return NULL;
+ }
+
+ return session;
+}
+
+/*
+ * pipex_ip_output - output an ip packet to the tunnel.
+ */
+void
+pipex_ip_output(struct mbuf *m0, struct pipex_session *session)
+{
+ int is_idle;
+ struct ifnet *ifp;
+
+ /* output succeed here as a interface */
+ ifp = session->pipex_iface->ifnet_this;
+ ifp->if_opackets++;
+ ifp->if_obytes+=m0->m_pkthdr.len;
+
+ if (session->is_multicast == 0) {
+ /*
+ * Multicast packet is a idle packet and it's not TCP.
+ */
+ if (session->ip_forward == 0)
+ goto drop;
+ /* reset 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 */
+ session->stat.idle_time = 0;
+ }
+
+ /* adjust tcpmss */
+ if ((session->ppp_flags & PIPEX_PPP_ADJUST_TCPMSS) != 0) {
+ m0 = adjust_tcp_mss(m0, session->peer_mru);
+ if (m0 == NULL)
+ goto drop;
+ }
+ } else {
+ m0->m_flags &= ~(M_BCAST|M_MCAST);
+ }
+
+ /* output ip packets to the session tunnel */
+ if (pipex_ppp_enqueue(m0, session, &pipexoutq))
+ /* enqueue failed. */
+ goto drop;
+
+ return;
+drop:
+ if (m0 != NULL)
+ m_freem(m0);
+ session->stat.oerrors++;
+ return;
+}
+
+static void
+pipex_ppp_output(struct mbuf *m0, struct pipex_session *session, int proto)
+{
+ u_char *cp, hdr[16];
+ int mppe = 0;
+
+#ifdef PIPEX_MPPE
+ if (pipex_session_is_mppe_enabled(session)) {
+ if (proto == PPP_IP)
+ mppe = 1;
+ }
+#endif /* PIPEX_MPPE */
+ cp = hdr;
+ if (!mppe && pipex_session_has_acf(session)) {
+ if (pipex_session_is_acfc_enabled(session)) {
+ /* address and control field compression */
+ } else {
+ PUTCHAR(PPP_ALLSTATIONS, cp);
+ PUTCHAR(PPP_UI, cp);
+ }
+ }
+ if (!mppe && pipex_session_is_pfc_enabled(session) && proto <= 0xff) {
+ PUTCHAR(proto, cp); /* protocol field compression */
+ } else {
+ PUTSHORT(proto, cp);
+ }
+
+ M_PREPEND(m0, cp - hdr, M_NOWAIT);
+ if (m0 == NULL)
+ goto drop;
+ memcpy(mtod(m0, u_char *), hdr, cp - hdr);
+
+#ifdef PIPEX_MPPE
+ if (mppe) {
+ pipex_mppe_output(m0, session);
+ return;
+ }
+#endif /* PIPEX_MPPE */
+
+ switch (session->protocol) {
+#ifdef PIPEX_PPPOE
+ case PIPEX_PROTO_PPPOE:
+ pipex_pppoe_output(m0, session);
+ break;
+#endif
+#ifdef PIPEX_PPTP
+ case PIPEX_PROTO_PPTP:
+ pipex_pptp_output(m0, session, 1, 1);
+ break;
+#endif
+ default:
+#ifdef DIAGNOSTIC
+ panic("NOTREACHED %s:%s:%d", __func__, __FILE__, __LINE__);
+#endif
+ goto drop;
+ }
+
+ return;
+drop:
+ if (m0 != NULL)
+ m_freem(m0);
+ session->stat.oerrors++;
+ return;
+}
+
+/*
+ * pipex_ppp_input
+ */
+static void
+pipex_ppp_input(struct mbuf *m0, struct pipex_session *session, int decrypted)
+{
+ int proto, hlen = 0;
+
+ switch ((proto = pipex_ppp_proto(m0, session, 0, &hlen))) {
+#ifdef PIPEX_MPPE
+ case PPP_CCP:
+ if (decrypted)
+ goto drop;
+ m_adj(m0, hlen);
+ pipex_ccp_input(m0, session);
+ return;
+ case PPP_COMP:
+ if (decrypted)
+ goto drop;
+ m_adj(m0, hlen);
+ pipex_mppe_input(m0, session);
+ return;
+#endif
+ case PPP_IP:
+ if (session->ip_forward == 0)
+ goto drop;
+ if (!decrypted && pipex_session_is_mppe_required(session))
+ /*
+ * if ip packet received when mppe
+ * is required, discard it.
+ */
+ goto drop;
+ m_adj(m0, hlen);
+ pipex_ip_input(m0, session);
+ return;
+ default:
+ if (decrypted)
+ goto drop;
+ KASSERT(session->protocol == PIPEX_PROTO_PPPOE);
+ /* will be proccessed by userland */
+ m_freem(m0);
+ return;
+ }
+
+ return;
+drop:
+ if (m0 != NULL)
+ m_freem(m0);
+ session->stat.ierrors++;
+
+ return;
+}
+
+/*
+ * pipex_ip_input
+ */
+static void
+pipex_ip_input(struct mbuf *m0, struct pipex_session *session)
+{
+ struct ifnet *ifp;
+ struct ifqueue *ifq = &ipintrq;
+ struct ip *ip;
+ int s, len;
+ int is_idle;
+
+ /* change recvif */
+ m0->m_pkthdr.rcvif = session->pipex_iface->ifnet_this;
+ ifp = m0->m_pkthdr.rcvif;
+
+ PIPEX_PULLUP(m0, sizeof(struct ip));
+ if (m0 == NULL)
+ goto drop;
+#if defined(__NetBSD__)
+ /* some components assume ip packet is aligned. */
+ if (!ALIGNED_POINTER(mtod(m0, caddr_t), struct ip *)) {
+ if ((m0 = m_copyup(m0, sizeof(struct ip),
+ ((max_linkhdr + 3) & ~3))) == NULL)
+ goto drop;
+ }
+#endif
+
+ /* 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) {
+ pipex_session_log(session, LOG_DEBUG,
+ "ip packet discarded by ingress filter (src %s)",
+ inet_ntoa(ip->ip_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 */
+ session->stat.idle_time = 0;
+ }
+
+ /* adjust tcpmss */
+ if (session->ppp_flags & PIPEX_PPP_ADJUST_TCPMSS) {
+ m0 = adjust_tcp_mss(m0, session->peer_mru);
+ if (m0 == NULL)
+ goto drop;
+ }
+
+ len = m0->m_pkthdr.len;
+
+#if NBPFILTER > 0
+ if (ifp->if_bpf)
+ bpf_mtap_af(ifp->if_bpf, AF_INET, m0, BPF_DIRECTION_IN);
+#endif
+
+#ifdef GATEWAY
+ if (ipflow_fastforward(m0))
+ goto done;
+#endif
+
+ /* enqueue to ipintrq */
+ s = splnet();
+ if (IF_QFULL(ifq)) {
+ IF_DROP(ifq);
+ splx(s);
+ ifp->if_collisions++; /* same as if_tun or if_pppac */
+ goto drop;
+ }
+ IF_ENQUEUE(ifq, m0);
+ splx(s);
+ schednetisr(NETISR_IP);
+
+#ifdef GATEWAY
+done:
+#endif
+ /* countup statistics */
+ ifp->if_ipackets++;
+ ifp->if_ibytes += len;
+ session->stat.ipackets++;
+ session->stat.ibytes += len;
+
+ return;
+drop:
+ if (m0 != NULL)
+ m_freem(m0);
+ session->stat.ierrors++;
+
+ return;
+}
+
+/*
+ * pipex_ppp_proto
+ */
+static inline int
+pipex_ppp_proto(struct mbuf *m0, struct pipex_session *session, int off,
+ int *hlenp)
+{
+ int proto;
+ u_char *cp, pktbuf[4];
+
+ 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 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), (caddr_t)&pppoe);
+ 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
+
+ return session;
+}
+
+struct mbuf *
+pipex_pppoe_input(struct mbuf *m0, struct pipex_session *session)
+{
+ 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), (caddr_t)&pppoe);
+
+ /* cut off PPPoE Header */
+ m_adj(m0, sizeof(struct ether_header) +
+ sizeof(struct pipex_pppoe_header));
+#if defined(__NetBSD__)
+ /* cut off FCS */
+ if (m0->m_flags & M_HASFCS) {
+ m0->m_flags &= ~M_HASFCS;
+ m_adj(m0, -ETHER_CRC_LEN);
+ }
+#endif
+
+ /* ensure the mbuf length equals the PPP frame length */
+ pppoe.length = ntohs(pppoe.length);
+ if (pppoe.length < PIPEX_PPPMINLEN)
+ goto drop;
+ if (m0->m_pkthdr.len < pppoe.length)
+ goto drop;
+ if (m0->m_pkthdr.len > pppoe.length) {
+ if (m0->m_len == m0->m_pkthdr.len) {
+ m0->m_len = pppoe.length;
+ m0->m_pkthdr.len = pppoe.length;
+ } else
+ m_adj(m0, pppoe.length - m0->m_pkthdr.len);
+ }
+
+ /* input ppp packets to kernel session */
+ if (pipex_ppp_enqueue(m0, session, &pipexinq))
+ goto drop;
+
+ return NULL;
+
+drop:
+ if (m0 != NULL)
+ m_freem(m0);
+ session->stat.ierrors++;
+ return NULL;
+}
+
+/*
+ * pipex_ppope_output
+ */
+static void
+pipex_pppoe_output(struct mbuf *m0, struct pipex_session *session)
+{
+ struct pipex_pppoe_header *pppoe;
+ struct ifnet *ifp, *over_ifp;
+ int len, padlen;
+
+ /* save length for pppoe header */
+ len = m0->m_pkthdr.len;
+
+ ifp = session->pipex_iface->ifnet_this;
+ over_ifp = session->proto.pppoe.over_ifp;
+
+ /* 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__));
+ session->stat.oerrors++;
+ return;
+ }
+ padlen = ETHERMIN - m0->m_pkthdr.len;
+ if (padlen > 0) {
+ m_copyback(m0, m0->m_pkthdr.len, padlen, pipex_pppoe_padding);
+ }
+
+ /* 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.rcvif = ifp;
+ m0->m_flags &= ~(M_BCAST|M_MCAST);
+
+ session->stat.opackets++;
+ session->stat.obytes += len;
+
+ over_ifp->if_output(
+ over_ifp, m0, &session->proto.pppoe.peer_addr, NULL);
+
+ return;
+}
+#endif /* PIPEX_PPPOE */
+
+#ifdef PIPEX_PPTP
+/***********************************************************************
+ * PPTP
+ ***********************************************************************/
+
+/*
+ * pipex_pptp_output
+ */
+static 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;
+
+ 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 *);
+
+#if 0
+ /*
+ * On NetBSD 1.6 or FreeBSD, ip_ouput assumes ip#ip_len must be host
+ * byte order.
+ */
+ ip->ip_len = m0->m_pkthdr.len;
+#else
+ ip->ip_len = htons(m0->m_pkthdr.len);
+#endif
+ ip->ip_off = 0;
+ ip->ip_ttl = MAXTTL;
+ ip->ip_p = IPPROTO_GRE;
+ ip->ip_tos = 0;
+
+ ip->ip_src = session->proto.pptp.our_address;
+ ip->ip_dst = session->proto.pptp.peer_address;
+
+ /* 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;
+ PUTLONG(session->proto.pptp.rcv_nxt - 1, cp);
+ }
+ gre->flags = htons(gre->flags);
+
+
+#if defined(__NetBSD__)
+ if (!ALIGNED_POINTER(mtod(m0, caddr_t), struct ip *)) {
+ /* some components assume ip packet is aligned. */
+ if ((m0 = m_copyup(m0, sizeof(struct ip),
+ ((max_linkhdr + 3) & ~3))) == NULL) {
+ goto drop;
+ }
+ }
+#endif
+
+ m0->m_pkthdr.rcvif = session->pipex_iface->ifnet_this;
+ if (ip_output(m0, NULL, NULL, 0, NULL, NULL) != 0) {
+ PIPEX_DBG((session, LOG_DEBUG, "ip_output failed."));
+ goto drop;
+ }
+ if (len > 0) { /* network layer only */
+ /* countup statistics */
+ session->stat.opackets++;
+ session->stat.obytes += len;
+ }
+
+ return;
+drop:
+ session->stat.oerrors++;
+
+ return;
+}
+
+/*
+ * pipex_pptp_lookup_session
+ */
+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;
+
+ /* pullup */
+ 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), (caddr_t)&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), (caddr_t)&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;
+ }
+
+ /* 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;
+}
+
+/*
+ * pipex_pptp_input
+ */
+struct mbuf *
+pipex_pptp_input(struct mbuf *m0, struct pipex_session *session)
+{
+ int hlen, plen, ppphlen, has_seq, has_ack, nseq, proto;
+ const char *reason = "";
+ u_char *cp, *seqp = NULL, *ackp = NULL, code;
+ uint32_t flags, seq = 0, ack = 0;
+ struct ip *ip;
+ struct pipex_gre_header *gre;
+ struct pipex_pptp_session *pptp_session;
+
+ 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);
+ }
+ 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)) {
+ reason = "ack out of sequence";
+ goto inseq;
+ } else if (SEQ32_GT(ack, pptp_session->snd_nxt)) {
+ reason = "ack for unknown sequence";
+ goto inseq;
+ } else {
+ ack++;
+ pptp_session->snd_una = ack;
+ }
+ }
+ if (!has_seq) {
+ /* ack only packet */
+ goto not_ours;
+ }
+ if (SEQ32_LT(seq, pptp_session->rcv_nxt)) {
+ reason = "out of sequence";
+ goto inseq;
+ } 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);
+ pptp_session->rcv_nxt = seq;
+
+ if (SEQ32_SUB(seq, pptp_session->rcv_acked) >
+ RUPDIV(pptp_session->winsz, 2)) {
+ /* Send ack only packet. */
+ pipex_pptp_output(NULL, session, 0, 1);
+ }
+
+ if (m0->m_pkthdr.len < hlen + PIPEX_PPPMINLEN)
+ 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, (caddr_t)&code);
+ if (code != CCP_RESETREQ && code != CCP_RESETACK)
+ goto not_ours;
+ break;
+
+ case PPP_COMP:
+#endif
+ case PPP_IP:
+ break;
+
+ default:
+ goto not_ours;
+ }
+
+ /* ok, The packet is for PIPEX */
+ session->proto.pptp.rcv_gap += nseq;
+ plen = ntohs(gre->len); /* payload length */
+ m_adj(m0, hlen); /* cut off the IP/GRE 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);
+ }
+
+ /* input ppp packets to kernel session */
+ if (pipex_ppp_enqueue(m0, session, &pipexinq))
+ goto drop;
+
+ return NULL;
+not_ours:
+ --seq; --ack; /* revert original values */
+ /*
+ * 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);
+ }
+
+ return m0;
+inseq:
+ 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);
+
+ /* FALLTHROUGH */
+drop:
+ if (m0 != NULL)
+ m_freem(m0);
+ session->stat.ierrors++;
+
+ return NULL;
+}
+
+/*
+ * pipex_pptp_userland_lookup_session
+ */
+struct pipex_session *
+pipex_pptp_userland_lookup_session(struct mbuf *m0, struct in_addr dst)
+{
+ 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), (caddr_t)&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);
+
+ list = PIPEX_PEER_ADDR_HASHTABLE(dst.s_addr);
+ LIST_FOREACH(session, list, peer_addr_chain) {
+ if (session->proto.pptp.peer_address.s_addr
+ != dst.s_addr)
+ continue;
+ if (session->peer_session_id == id)
+ break;
+ }
+#ifdef PIPEX_DEBUG
+ if (session == NULL) {
+ PIPEX_DBG((NULL, LOG_DEBUG,
+ "<%s> session not found (dst=%s,call_id=%d)",
+ __func__, inet_ntoa(dst), (int)gre.call_id));
+ }
+#endif
+ return session;
+}
+
+/*
+ * pipex_pptp_userland_output
+ */
+struct mbuf *
+pipex_pptp_userland_output(struct mbuf *m0, struct pipex_session *session)
+{
+ struct pipex_gre_header *gre;
+ uint16_t flags;
+ u_char *cp, *cp0;
+ uint32_t val32;
+
+ /* check length */
+ PIPEX_PULLUP(m0, sizeof(struct pipex_gre_header) + 8);
+ 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 *);
+ flags = ntohs(gre->flags);
+
+ /*
+ * 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;
+ }
+
+ return m0;
+}
+#endif /* PIPEX_PPTP */
+
+#ifdef PIPEX_MPPE
+/**********************************************************************
+ * MPPE
+ ***********************************************************************/
+#define PIPEX_COHERENCY_CNT_MASK 0x0fff
+
+static void
+pipex_mppe_req_init(struct pipex_mppe_req *mppe_req, struct pipex_mppe *mppe)
+{
+ if (mppe_req->stateless)
+ mppe->stateless = 1;
+ memcpy(mppe->master_key, mppe_req->master_key,sizeof(mppe->master_key));
+
+ mppe->keylenbits = mppe_req->keylenbits;
+ switch (mppe_req->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);
+ rc4_key(mppe, mppe->keylen, mppe->session_key);
+}
+
+#if defined(__NetBSD__)
+#include <sys/sha1.h>
+#else
+#include <crypto/sha1.h>
+#endif
+
+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,
+};
+
+
+#if defined(__OpenBSD__)
+#define arc4_setkey(_ctx, _key, _keylen) \
+ rc4_keysetup((struct rc4_ctx *)(_ctx), (_key), (_keylen))
+#define arc4_encrypt(_ctx, _out, _in, _len) \
+ rc4_crypt((struct rc4_ctx *)(_ctx), (_in), (_out), (_len))
+#endif
+
+static inline int
+rc4_key(struct pipex_mppe *mppe, int lkey, u_char *key)
+{
+
+ arc4_setkey(&mppe->rc4ctx, key, lkey);
+
+ return 0;
+}
+
+static inline void
+rc4(struct pipex_mppe *mppe, int len, u_char *indata, u_char *outdata)
+{
+
+ arc4_encrypt(&mppe->rc4ctx, outdata, indata, len);
+}
+
+#define ZeroMemory(dst, len) memset(dst, 0, len)
+#define MoveMemory(dst, src, len) memcpy(dst, src, len)
+#define SHA_CTX SHA1_CTX
+#define SHAInit SHA1Init
+#define SHAUpdate SHA1Update
+#define SHAFinal(ctx,digest) SHA1Final(digest, ctx)
+
+static void
+GetNewKeyFromSHA(StartKey, SessionKey, SessionKeyLength, InterimKey)
+ u_char *StartKey;
+ u_char *SessionKey;
+ int SessionKeyLength;
+ u_char *InterimKey;
+{
+ u_char Digest[20];
+ SHA_CTX Context;
+
+ ZeroMemory(Digest, 20);
+
+ SHAInit(&Context);
+ SHAUpdate(&Context, StartKey, SessionKeyLength);
+ SHAUpdate(&Context, SHAPad1, 40);
+ SHAUpdate(&Context, SessionKey, SessionKeyLength);
+ SHAUpdate(&Context, SHAPad2, 40);
+ SHAFinal(&Context, Digest);
+
+ MoveMemory(InterimKey, Digest, SessionKeyLength);
+}
+
+/*
+ * pipex_mppe_reduce_key
+ */
+static void
+pipex_mppe_reduce_key(struct pipex_mppe *mppe)
+{
+ switch (mppe->keylenbits) {
+ case 40:
+ mppe->session_key[1] = 0x26;
+ mppe->session_key[2] = 0x9e;
+ case 56:
+ mppe->session_key[0] = 0xd1;
+ }
+}
+
+/*
+ * mppe_key_change
+ */
+static void
+mppe_key_change(struct pipex_mppe *mppe)
+{
+ u_char interim[16];
+ struct pipex_mppe keychg; /* just for rc4ctx */
+
+ memset(&keychg, 0, sizeof(keychg));
+
+ GetNewKeyFromSHA(mppe->master_key, mppe->session_key,
+ mppe->keylen, interim);
+
+ rc4_key(&keychg, mppe->keylen, interim);
+ rc4(&keychg, mppe->keylen, interim, mppe->session_key);
+ pipex_mppe_reduce_key(mppe);
+}
+
+/*
+ * mppe_decap - mppe decapsulation and payload decryption
+ */
+static void
+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;
+
+ /* 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;
+
+ PIPEX_MPPE_DBG((session, LOG_DEBUG, "in coher_cnt=%03x %s%s",
+ mppe->coher_cnt, (flushed) ? "[flushed]" : "",
+ (encrypt) ? "[encrypt]" : ""));
+
+ if (encrypt == 0) {
+ pipex_session_log(session, LOG_DEBUG,
+ "Received unexpected MPPE packet.(no ecrypt)");
+ goto drop;
+ }
+
+ /* adjust mbuf */
+ m_adj(m0, sizeof(coher_cnt));
+
+#ifdef WORKAROUND_OUT_OF_SEQUENCE_PPP_FRAMING
+ /*
+ * 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) {
+ pipex_session_log(session, LOG_DEBUG,
+ "Workaround the out-of-sequence PPP framing problem: "
+ "%d => %d", mppe->coher_cnt, coher_cnt);
+ goto drop;
+ }
+ }
+#endif
+ if (mppe->stateless != 0) {
+ mppe_key_change(mppe);
+ while (mppe->coher_cnt != coher_cnt) {
+ mppe_key_change(mppe);
+ mppe->coher_cnt++;
+ mppe->coher_cnt &= PIPEX_COHERENCY_CNT_MASK;
+ pktloss++;
+ }
+ flushed = 1;
+ } 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) {
+ /* Send CCP ResetReq */
+ PIPEX_DBG((session, LOG_DEBUG, "CCP SendResetReq"));
+ pipex_ccp_output(session, CCP_RESETREQ,
+ session->ccp_id++);
+ goto drop;
+ }
+ if ((coher_cnt & 0xff) == 0xff) {
+ mppe_key_change(mppe);
+ flushed = 1;
+ }
+ }
+#ifndef WORKAROUND_OUT_OF_SEQUENCE_PPP_FRAMING
+ if (pktloss > 1000) {
+ pipex_session_log(session, LOG_DEBUG,
+ "%d packets loss.", pktloss);
+ }
+#endif
+ if (flushed)
+ rc4_key(mppe, mppe->keylen, mppe->session_key);
+
+ /* decrypt ppp payload */
+ for (m1 = m0; m1; m1 = m1->m_next) {
+ cp = mtod(m1, u_char *);
+ len = m1->m_len;
+ rc4(mppe, len, cp, cp);
+ }
+
+ /* update coher_cnt */
+ mppe->coher_cnt++;
+ mppe->coher_cnt &= PIPEX_COHERENCY_CNT_MASK;
+
+ pipex_ppp_input(m0, session, 1);
+
+ return;
+drop:
+ if (m0 != NULL)
+ m_freem(m0);
+ session->stat.ierrors++;
+
+ return;
+}
+
+/*
+ * pipex_mppe_output - mppe payload encryption and encapsulation
+ */
+static void
+pipex_mppe_output(struct mbuf *m0, struct pipex_session *session)
+{
+ int encrypt, flushed, len;
+ uint16_t coher_cnt;
+ u_char *cp;
+ struct pipex_mppe *mppe;
+ struct mbuf *m;
+
+ mppe = &session->mppe_send;
+
+ /* prepend mppe header */
+ M_PREPEND(m0, sizeof(coher_cnt), M_NOWAIT);
+ if (m0 == NULL)
+ goto drop;
+ m0 = m_pullup(m0, 2);
+ if (m0 == NULL)
+ goto drop;
+ /*
+ * create a deep-copy if the mbuf has a shared mbuf cluster.
+ * this is required to handle cases of tcp retransmition.
+ */
+ for (m = m0; m != NULL; m = m->m_next) {
+ if (M_READONLY(m)) {
+#if 1
+ m = m_copym2(m0, 0, M_COPYALL, M_NOWAIT);
+#else
+ m = m_dup(m0, 0, M_COPYALL, M_NOWAIT);
+#endif
+ if (m == NULL)
+ goto drop;
+ m_freem(m0);
+ m0 = m;
+ break;
+ }
+ }
+ cp = mtod(m0, u_char *);
+
+ /* check coherency counter */
+ flushed = 0;
+ encrypt = 1;
+
+ if (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->resetreq != 0) {
+ flushed = 1;
+ mppe->resetreq = 0;
+ }
+ }
+
+ if (flushed)
+ rc4_key(mppe, mppe->keylen, mppe->session_key);
+
+ PIPEX_MPPE_DBG((session, LOG_DEBUG, "out coher_cnt=%03x %s%s",
+ mppe->coher_cnt, (flushed) ? "[flushed]" : "",
+ (encrypt) ? "[encrypt]" : ""));
+
+ /* setup header information */
+ coher_cnt = (mppe->coher_cnt++) & PIPEX_COHERENCY_CNT_MASK;
+ mppe->coher_cnt &= PIPEX_COHERENCY_CNT_MASK;
+ if (flushed)
+ coher_cnt |= 0x8000;
+ if (encrypt)
+ coher_cnt |= 0x1000;
+
+ PUTSHORT(coher_cnt, cp);
+ len = m0->m_len - 2;
+ rc4(mppe, len, cp, cp);
+
+ /* encrypt chain */
+ for (m = m0->m_next; m; m = m->m_next) {
+ cp = mtod(m, u_char *);
+ len = m->m_len;
+ rc4(mppe, len, cp, cp);
+ }
+
+ pipex_ppp_output(m0, session, PPP_COMP);
+
+ return;
+drop:
+ session->stat.oerrors++;
+}
+
+static 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"));
+ session->mppe_send.resetreq = 1;
+#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:
+ if (m0 != NULL)
+ m_freem(m0);
+ session->stat.ierrors++;
+}
+
+static 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) {
+ session->stat.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 fuctions
+ ***********************************************************************/
+/* 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.
+ */
+static 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;
+
+ 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 */
+ break;
+ case TCPOPT_EOL:
+ goto handled;
+ /* NOTREACHED */
+ break;
+ case TCPOPT_NOP:
+ lpktp--;
+ break;
+ default:
+ GETCHAR(optlen, pktp);
+ pktp += 2 - optlen;
+ lpktp -= optlen;
+ break;
+ }
+ }
+
+handled:
+ return m0;
+
+drop:
+ if (m0)
+ 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)
+ */
+static 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;
+
+ 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;
+ uh = mtod(m0, struct udphdr *);
+
+ 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;
+}
+
+/*
+ * log for the pipex_session.
+ */
+static void
+pipex_session_log(struct pipex_session *session, int prio, const char *fmt, ...)
+{
+ char logbuf[1024];
+ va_list ap;
+
+ logpri(prio);
+ if (session != NULL) {
+ addlog("pipex: ppp=%d iface=%s protocol=%s id=%d ",
+ session->ppp_id, session->pipex_iface->ifnet_this->if_xname,
+ (session->protocol == PIPEX_PROTO_PPPOE)? "PPPoE" :
+ (session->protocol == PIPEX_PROTO_PPTP)? "PPTP" : "Unknown",
+ session->session_id);
+ } else
+ addlog("pipex: ");
+
+ va_start(ap, fmt);
+ vsnprintf(logbuf, sizeof(logbuf), fmt, ap);
+ va_end(ap);
+ addlog("%s\n", logbuf);
+
+ return;
+}
diff --git a/sys/net/pipex.h b/sys/net/pipex.h
new file mode 100644
index 00000000000..97d83126e02
--- /dev/null
+++ b/sys/net/pipex.h
@@ -0,0 +1,166 @@
+/* $Id: pipex.h,v 1.1 2010/01/11 03:50:56 yasuoka 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.
+ */
+
+#ifndef NET_PIPEX_H
+#define NET_PIPEX_H 1
+
+#define PIPEX_ENABLE 1
+#define PIPEX_DISABLE 0
+
+#define PIPEX_PROTO_PPPOE 0x8864 /* protocol pppoe */
+#define PIPEX_PROTO_PPTP 0x880b /* protocol pptp */
+#define PIPEX_MAX_LISTREQ 128 /* list request size */
+#define PIPEX_MPPE_KEYLEN 16
+
+/* pipex_mppe */
+struct pipex_mppe_req {
+ int16_t stateless; /* mppe key mode */
+ int16_t keylenbits; /* mppe key length(in bits)*/
+ u_char master_key[PIPEX_MPPE_KEYLEN]; /* mppe mastter key */
+};
+
+/* pppac ip-extension forwarded statistics */
+struct pipex_statistics {
+ uint32_t ipackets; /* tunnel to network */
+ uint32_t ierrors; /* tunnel to network */
+ uint64_t ibytes; /* tunnel to network */
+ uint32_t opackets; /* network to tunnel */
+ uint32_t oerrors; /* network to tunnel */
+ uint64_t obytes; /* network to tunnel */
+
+ uint32_t idle_time; /* idle timer */
+};
+
+struct pipex_session_req {
+ int pr_protocol; /* tunnel protocol */
+ uint16_t pr_session_id; /* session-id */
+ uint16_t pr_peer_session_id; /* peer's session-id */
+ uint32_t pr_ppp_flags; /* PPP configuration flags */
+#define PIPEX_PPP_ACFC_ACCEPTED 0x00000001
+#define PIPEX_PPP_PFC_ACCEPTED 0x00000002
+#define PIPEX_PPP_ACFC_ENABLED 0x00000004
+#define PIPEX_PPP_PFC_ENABLED 0x00000008
+#define PIPEX_PPP_MPPE_ACCEPTED 0x00000010
+#define PIPEX_PPP_MPPE_ENABLED 0x00000020
+#define PIPEX_PPP_MPPE_REQUIRED 0x00000040
+#define PIPEX_PPP_HAS_ACF 0x00000080
+#define PIPEX_PPP_ADJUST_TCPMSS 0x00000100
+ int8_t pr_ccp_id; /* CCP current packet id */
+ int pr_ppp_id; /* PPP Id. */
+ uint16_t pr_peer_mru; /* Peer's MRU */
+ uint16_t pr_timeout_sec; /* Idle Timer */
+
+ struct in_addr pr_ip_address; /* framed IP-Address */
+ struct in_addr pr_ip_netmask; /* framed IP-Netmask */
+ union {
+ struct {
+ uint32_t snd_nxt; /* send next */
+ uint32_t rcv_nxt; /* receive next */
+ uint32_t snd_una; /* unacked */
+ uint32_t rcv_acked; /* recv acked */
+ int winsz; /* window size */
+ int maxwinsz; /* max window size */
+ int peer_maxwinsz; /* peer's max window size */
+ struct in_addr peer_address; /* peer's IP address */
+ struct in_addr our_address; /* our IP address */
+ } pptp;
+ struct {
+ char over_ifname[IF_NAMESIZE]; /* ethernet i/f name */
+ struct ether_addr peer_address;/* peer's ether address*/
+ } pppoe;
+ } pr_proto;
+ struct pipex_mppe_req pr_mppe_recv;
+ struct pipex_mppe_req pr_mppe_send;
+};
+
+struct pipex_session_stat_req {
+ int psr_protocol; /* tunnel protocol */
+ uint16_t psr_session_id; /* session-id */
+ struct pipex_statistics psr_stat; /* statistics */
+};
+#define pipex_session_close_req pipex_session_stat_req
+#define pcr_protocol psr_protocol
+#define pcr_session_id psr_session_id
+#define pcr_stat psr_stat
+
+struct pipex_session_list_req {
+ uint8_t plr_flags;
+#define PIPEX_LISTREQ_NONE 0x00
+#define PIPEX_LISTREQ_MORE 0x01
+ int plr_ppp_id_count; /* count of PPP id */
+ int plr_ppp_id[PIPEX_MAX_LISTREQ]; /* PPP id */
+};
+
+struct pipex_session_config_req {
+ int pcr_protocol; /* tunnel protocol */
+ uint16_t pcr_session_id; /* session-id */
+ int pcr_ip_forward; /* ip_forwarding on/off */
+};
+
+/* PIPEX ioctls */
+#define PIPEXSMODE _IOW ('t', 91, int)
+#define PIPEXGMODE _IOR ('t', 92, int)
+#define PIPEXASESSION _IOW ('t', 94, struct pipex_session_req)
+#define PIPEXDSESSION _IOWR('t', 93, struct pipex_session_close_req)
+#define PIPEXCSESSION _IOW ('t', 95, struct pipex_session_config_req)
+#define PIPEXGSTAT _IOWR('t', 96, struct pipex_session_stat_req)
+#define PIPEXGCLOSED _IOR ('t', 97, struct pipex_session_list_req)
+
+#ifdef _KERNEL
+
+struct pipex_session;
+
+/* pipex context for a interface. */
+struct pipex_iface_context {
+ struct ifnet *ifnet_this; /* outer interface */
+ u_int pipexmode; /* pppac ipex mode */
+ /* virtual pipex_session entry for multicast routing */
+ struct pipex_session *multicast_session;
+};
+#include <sys/cdefs.h>
+__BEGIN_DECLS
+void pipex_init (void);
+void pipex_iface_init (struct pipex_iface_context *, struct ifnet *);
+void pipex_iface_start (struct pipex_iface_context *);
+void pipex_iface_stop (struct pipex_iface_context *);
+
+int pipex_notify_close_session(struct pipex_session *session);
+int pipex_notify_close_session_all(void);
+
+struct pipex_session *pipex_ip_lookup_session (struct mbuf *, struct pipex_iface_context *);
+void pipex_ip_output (struct mbuf *, struct pipex_session *);
+struct pipex_session *pipex_pppoe_lookup_session (struct mbuf *);
+struct mbuf *pipex_pppoe_input (struct mbuf *, struct pipex_session *);
+struct pipex_session *pipex_pptp_lookup_session (struct mbuf *);
+struct mbuf *pipex_pptp_input (struct mbuf *, struct pipex_session *);
+struct pipex_session *pipex_pptp_userland_lookup_session (struct mbuf *, struct in_addr);
+struct mbuf *pipex_pptp_userland_output (struct mbuf *, struct pipex_session *);
+int pipex_ioctl (struct pipex_iface_context *, int, caddr_t);
+__END_DECLS
+
+#endif /* _KERNEL */
+#endif
diff --git a/sys/net/pipex_local.h b/sys/net/pipex_local.h
new file mode 100644
index 00000000000..9f5b4cc3c3a
--- /dev/null
+++ b/sys/net/pipex_local.h
@@ -0,0 +1,319 @@
+/* $Id: pipex_local.h,v 1.1 2010/01/11 03:50:56 yasuoka 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.
+ */
+#define PIPEX_PPTP 1
+#define PIPEX_PPPOE 1
+#define PIPEX_MPPE 1
+
+#define PIPEX_ENABLED 0x0001
+
+#ifndef LOG_PPPAC
+#define LOG_PPPAC LOG_KERN
+#endif
+
+/* compile time option constants */
+#ifndef PIPEX_MAX_SESSION
+#define PIPEX_MAX_SESSION 512
+#endif
+#ifdef NBSAFE_RC4
+#define PIPEX_RC4_CTXLEN 288
+#else
+#define PIPEX_RC4_CTXLEN 1152
+#endif
+#define PIPEX_HASH_DIV 8
+#define PIPEX_HASH_SIZE (PIPEX_MAX_SESSION/PIPEX_HASH_DIV)
+#define PIPEX_HASH_MASK (PIPEX_HASH_SIZE-1)
+#define PIPEX_CLOSE_TIMEOUT 30
+#define PIPEX_DEQUEUE_LIMIT (IFQ_MAXLEN >> 1)
+#define PIPEX_PPPMINLEN 5
+ /* minimum PPP header length is 1 and minimum ppp payload length is 4 */
+
+#ifndef NNBY /* usually defined on the <sys/types.h> */
+#define NNBY 8 /* number of bits of a byte */
+#endif
+
+#ifdef PIPEX_MPPE
+/* mppe rc4 key */
+struct pipex_mppe {
+ int16_t stateless:1, /* key change mode */
+ resetreq:1,
+ reserved:14;
+ int16_t keylenbits; /* key length */
+ int16_t keylen;
+ uint16_t coher_cnt; /* cohency counter */
+ struct {
+ u_char rc4ctx_space[PIPEX_RC4_CTXLEN]; /* space for rc4ctx */
+ } rc4ctx; /* rc4ctx */
+ u_char master_key[PIPEX_MPPE_KEYLEN]; /* master key of MPPE */
+ u_char session_key[PIPEX_MPPE_KEYLEN]; /* session key of MPPE */
+};
+#endif /* PIPEX_MPPE */
+
+#ifdef PIPEX_PPPOE
+struct pipex_pppoe_session {
+ struct ifnet *over_ifp; /* ether inteface */
+ struct sockaddr peer_addr; /* peer's sockaddr */
+};
+#endif /* PIPEX_PPPOE */
+
+#ifdef PIPEX_PPTP
+struct pipex_pptp_session {
+ /* sequence number gap between pipex and userland */
+ int32_t snd_gap; /* gap of our sequence */
+ int32_t rcv_gap; /* gap of peer's sequence */
+ int32_t ul_snd_una; /* userland send acked seq */
+
+ uint32_t snd_nxt; /* send next */
+ uint32_t rcv_nxt; /* receive next */
+ uint32_t snd_una; /* send acked sequence */
+ uint32_t rcv_acked; /* recv acked sequence */
+
+ int winsz; /* windows size */
+ int maxwinsz; /* max windows size */
+ int peer_maxwinsz; /* peer's max windows size */
+
+ struct in_addr peer_address; /* inet destination address */
+ struct in_addr our_address; /* inet source address */
+};
+#endif /* PIPEX_PPTP */
+
+/* pppac ip-extension sessoin table */
+struct pipex_session {
+ struct radix_node ps4_rn[2]; /* tree glue, and other values */
+ LIST_ENTRY(pipex_session) session_list; /* all session chain */
+ LIST_ENTRY(pipex_session) state_list; /* state list chain */
+ LIST_ENTRY(pipex_session) id_chain; /* id hash chain */
+ LIST_ENTRY(pipex_session) peer_addr_chain;
+ /* peer's address hash chain */
+ uint16_t state; /* pipex session state */
+#define PIPEX_STATE_INITIAL 0x0000
+#define PIPEX_STATE_OPENED 0x0001
+#define PIPEX_STATE_CLOSE_WAIT 0x0002
+#define PIPEX_STATE_CLOSED 0x0003
+
+ uint16_t ip_forward:1, /* {en|dis}ableIP forwarding */
+ is_multicast:1, /* virtual entry for mutlicast */
+ reserved:14;
+ uint16_t protocol; /* tunnel protocol (PK) */
+ uint16_t session_id; /* session-id (PK) */
+ uint16_t peer_session_id; /* peer's session-id */
+ uint16_t peer_mru; /* peer's MRU */
+ uint32_t timeout_sec; /* idle timeout */
+ int ppp_id; /* PPP id */
+
+ struct sockaddr_in ip_address; /* remote address (AK) */
+ struct sockaddr_in ip_netmask; /* remote address mask (AK) */
+
+ struct pipex_iface_context* pipex_iface;/* context for interface */
+
+ uint32_t ppp_flags; /* configure flags */
+#ifdef PIPEX_MPPE
+ int ccp_id; /* CCP packet id */
+ struct pipex_mppe
+ mppe_recv, /* MPPE context for incoming */
+ mppe_send; /* MPPE context for outgoing */
+#endif /*PIPEXMPPE */
+ struct pipex_statistics stat; /* statistics */
+ union {
+#ifdef PIPEX_PPPOE
+ struct pipex_pppoe_session pppoe; /* context for PPPoE */
+#endif /* PIPEX_PPPOE */
+#ifdef PIPEX_PPTP
+ struct pipex_pptp_session pptp; /* context for PPTP */
+#endif /* PIPEX_PPTP */
+ char _proto_unknown[0];
+ } proto;
+};
+
+/* gre header */
+struct pipex_gre_header {
+ uint16_t flags; /* flags and version*/
+#define PIPEX_GRE_KFLAG 0x2000 /* keys present */
+#define PIPEX_GRE_SFLAG 0x1000 /* seq present */
+#define PIPEX_GRE_AFLAG 0x0080 /* ack present */
+#define PIPEX_GRE_VER 0x0001 /* gre version code */
+#define PIPEX_GRE_VERMASK 0x0003 /* gre version mask */
+
+ uint16_t type;
+#define PIPEX_GRE_PROTO_PPP 0x880b /* gre/ppp */
+
+ uint16_t len; /* length not include gre header */
+ uint16_t call_id; /* call_id */
+} __attribute__((__packed__));
+
+/* ppp/ccp header */
+struct pipex_ccp_header {
+ uint8_t code;
+ uint8_t id;
+ uint16_t len;
+} __attribute__((__packed__));
+
+/* ppp header */
+struct pipex_ppp_header {
+ uint8_t c[0]; /* ppp proto */
+ uint16_t code; /* ppp proto (2 oct) */
+} __attribute__((__packed__));
+
+
+/* pppoe header */
+struct pipex_pppoe_header {
+ uint8_t vertype; /* version and type */
+#define PIPEX_PPPOE_VERTYPE 0x11 /* version and type code */
+
+ uint8_t code; /* code */
+#define PIPEX_PPPOE_CODE_SESSION 0x00 /* code session */
+
+ uint16_t session_id; /* session id */
+ uint16_t length; /* length */
+} __attribute__((__packed__));
+
+#ifdef PIPEX_DEBUG
+#define PIPEX_DBG(a) if (pipex_debug & 1) pipex_session_log a
+/* #define PIPEX_MPPE_DBG(a) if (pipex_debug & 1) pipex_session_log a */
+#define PIPEX_MPPE_DBG(a)
+#else
+#define PIPEX_DBG(a)
+#define PIPEX_MPPE_DBG(a)
+#endif /* PIPEX_DEBUG */
+
+#define PIPEX_ID_HASHTABLE(key) \
+ (&pipex_id_hashtable[(key) & PIPEX_HASH_MASK])
+#define PIPEX_PEER_ADDR_HASHTABLE(key) \
+ (&pipex_peer_addr_hashtable[ntohl((key)) & PIPEX_HASH_MASK])
+#define PIPEX_ADDR_HASHTABLE(key) \
+ (&pipex_addr_hashtable[ntohl((key)) & PIPEX_HASH_MASK])
+
+#define GETCHAR(c, cp) { (c) = *(cp)++; }
+#define PUTCHAR(s, cp) { *(cp)++ = (u_char) (s); }
+#define GETSHORT(s, cp) { \
+ (s) = *(cp)++ << 8; \
+ (s) |= *(cp)++; \
+}
+#define PUTSHORT(s, cp) { \
+ *(cp)++ = (u_char) ((s) >> 8); \
+ *(cp)++ = (u_char) (s); \
+}
+#define GETLONG(l, cp) { \
+ (l) = *(cp)++ << 8; \
+ (l) |= *(cp)++; (l) <<= 8; \
+ (l) |= *(cp)++; (l) <<= 8; \
+ (l) |= *(cp)++; \
+}
+#define PUTLONG(l, cp) { \
+ *(cp)++ = (u_char) ((l) >> 24); \
+ *(cp)++ = (u_char) ((l) >> 16); \
+ *(cp)++ = (u_char) ((l) >> 8); \
+ *(cp)++ = (u_char) (l); \
+}
+#define PIPEX_PULLUP(m0, l) \
+ if ((m0)->m_len < (l)) { \
+ if ((m0)->m_pkthdr.len < (l)) { \
+ PIPEX_DBG((NULL, LOG_DEBUG, \
+ "<%s> received packet is too short.", \
+ __func__)); \
+ m_freem(m0); \
+ (m0) = NULL; \
+ } else { \
+ (m0) = m_pullup((m0), (l)); \
+ KASSERT((m0) != NULL); \
+ } \
+ }
+#define PIPEX_SEEK_NEXTHDR(ptr, len, t) \
+ ((t) (((char *)ptr) + len))
+#define SEQ32_LT(a,b) ((int)((a) - (b)) < 0)
+#define SEQ32_LE(a,b) ((int)((a) - (b)) <= 0)
+#define SEQ32_GT(a,b) ((int)((a) - (b)) > 0)
+#define SEQ32_GE(a,b) ((int)((a) - (b)) >= 0)
+#define SEQ32_SUB(a,b) ((int32_t)((a) - (b)))
+
+#define RUPDIV(n,d) (((n) + (d) - ((n) % (d))) / (d))
+#define pipex_session_is_acfc_accepted(s) \
+ (((s)->ppp_flags & PIPEX_PPP_ACFC_ACCEPTED)? 1 : 0)
+#define pipex_session_is_pfc_accepted(s) \
+ (((s)->ppp_flags & PIPEX_PPP_PFC_ACCEPTED)? 1 : 0)
+#define pipex_session_is_acfc_enabled(s) \
+ (((s)->ppp_flags & PIPEX_PPP_ACFC_ENABLED)? 1 : 0)
+#define pipex_session_is_pfc_enabled(s) \
+ (((s)->ppp_flags & PIPEX_PPP_PFC_ENABLED)? 1 : 0)
+#define pipex_session_has_acf(s) \
+ (((s)->ppp_flags & PIPEX_PPP_HAS_ACF)? 1 : 0)
+#define pipex_session_is_mppe_accepted(s) \
+ (((s)->ppp_flags & PIPEX_PPP_MPPE_ACCEPTED)? 1 : 0)
+#define pipex_session_is_mppe_enabled(s) \
+ (((s)->ppp_flags & PIPEX_PPP_MPPE_ENABLED)? 1 : 0)
+#define pipex_session_is_mppe_required(s) \
+ (((s)->ppp_flags & PIPEX_PPP_MPPE_REQUIRED)? 1 : 0)
+#define pipex_mppe_rc4_keybits(r) ((r)->keylen << 3)
+#define PIPEX_IPGRE_HDRLEN (sizeof(struct ip) + sizeof(struct pipex_gre_header))
+#define PIPEX_TCP_OPTLEN 40
+
+/*
+ * static function prototypes
+ */
+static int pipex_add_session (struct pipex_session_req *, struct pipex_iface_context *);
+static int pipex_close_session (struct pipex_session_close_req *);
+static int pipex_config_session (struct pipex_session_config_req *);
+static int pipex_get_stat (struct pipex_session_stat_req *);
+static int pipex_get_closed (struct pipex_session_list_req *);
+static int pipex_destroy_session (struct pipex_session *);
+static struct pipex_session *pipex_lookup_by_ip_address (struct in_addr);
+static struct pipex_session *pipex_lookup_by_session_id (int, int);
+static void pipex_ppp_output (struct mbuf *, struct pipex_session *, int);
+static inline int pipex_ppp_proto (struct mbuf *, struct pipex_session *, int, int *);
+static void pipex_ppp_input (struct mbuf *, struct pipex_session *, int);
+static void pipex_ip_input (struct mbuf *, struct pipex_session *);
+
+#ifdef PIPEX_PPPOE
+static void pipex_pppoe_output (struct mbuf *, struct pipex_session *);
+#endif
+
+#ifdef PIPEX_PPTP
+static void pipex_pptp_output (struct mbuf *, struct pipex_session *, int, int);
+#endif
+
+#ifdef PIPEX_MPPE
+static void pipex_mppe_req_init (struct pipex_mppe_req *, struct pipex_mppe *);
+static void GetNewKeyFromSHA (u_char *, u_char *, int, u_char *);
+static inline int rc4_key (struct pipex_mppe *, int, u_char *);
+static inline void rc4 (struct pipex_mppe *, int, u_char *, u_char *);
+static int rc4_key (struct pipex_mppe *, int, u_char *);
+static void rc4 (struct pipex_mppe *, int, u_char *, u_char *);
+static void pipex_mppe_reduce_key (struct pipex_mppe *);
+static void mppe_key_change (struct pipex_mppe *);
+static void pipex_mppe_input (struct mbuf *, struct pipex_session *);
+static void pipex_mppe_output (struct mbuf *, struct pipex_session *);
+static void pipex_ccp_input (struct mbuf *, struct pipex_session *);
+static int pipex_ccp_output (struct pipex_session *, int, int);
+#endif
+
+static struct mbuf *adjust_tcp_mss (struct mbuf *, int);
+static struct mbuf *ip_is_idle_packet (struct mbuf *, int *);
+static void pipex_session_log (struct pipex_session *, int, const char *, ...) __attribute__((__format__(__printf__,3,4)));
+static int pipex_ppp_enqueue (struct mbuf *, struct pipex_session *, struct ifqueue *);
+static void pipex_ppp_dequeue (void);
+static void pipex_timer_start (void);
+static void pipex_timer_stop (void);
+static void pipex_timer (void *);
diff --git a/sys/sys/mbuf.h b/sys/sys/mbuf.h
index bf79b4c504d..f4abccd3c37 100644
--- a/sys/sys/mbuf.h
+++ b/sys/sys/mbuf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: mbuf.h,v 1.139 2009/11/03 10:59:04 claudio Exp $ */
+/* $OpenBSD: mbuf.h,v 1.140 2010/01/11 03:50:56 yasuoka Exp $ */
/* $NetBSD: mbuf.h,v 1.19 1996/02/09 18:25:14 christos Exp $ */
/*
@@ -445,6 +445,7 @@ struct m_tag *m_tag_next(struct mbuf *, struct m_tag *);
#define PACKET_TAG_GRE 0x0080 /* GRE processing done */
#define PACKET_TAG_DLT 0x0100 /* data link layer type */
#define PACKET_TAG_PF_DIVERT 0x0200 /* pf(4) diverted packet */
+#define PACKET_TAG_PIPEX 0x0400 /* pipex context XXX */
#ifdef MBTYPES
int mbtypes[] = { /* XXX */