X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;f=pppd%2Fplugins%2Fpppol2tp%2Fpppol2tp.c;fp=pppd%2Fplugins%2Fpppol2tp%2Fpppol2tp.c;h=a7e3400e72faed5ac0fac7991910af704f7920bf;hb=c3480c5c56919a12f7bd34f8a9e193922c154fe9;hp=0000000000000000000000000000000000000000;hpb=b82a6c7601812f4079fe641e42009f263a487af2;p=ppp.git diff --git a/pppd/plugins/pppol2tp/pppol2tp.c b/pppd/plugins/pppol2tp/pppol2tp.c new file mode 100644 index 0000000..a7e3400 --- /dev/null +++ b/pppd/plugins/pppol2tp/pppol2tp.c @@ -0,0 +1,526 @@ +/* pppol2tp.c - pppd plugin to implement PPPoL2TP protocol + * for Linux using kernel pppol2tp support. + * + * Requires kernel pppol2tp driver which is integrated into the kernel + * from 2.6.23 onwards. For earlier kernels, a version can be obtained + * from the OpenL2TP project at + * http://www.sourceforge.net/projects/openl2tp/ + * + * Original by Martijn van Oosterhout + * Modified by jchapman@katalix.com + * + * Heavily based upon pppoatm.c: original notice follows + * + * Copyright 2000 Mitchell Blank Jr. + * Based in part on work from Jens Axboe and Paul Mackerras. + * Updated to ppp-2.4.1 by Bernhard Kaindl + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include +#include +#include +#include +#include "pppd.h" +#include "pathnames.h" +#include "fsm.h" +#include "lcp.h" +#include "ccp.h" +#include "ipcp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef aligned_u64 +/* should be defined in sys/types.h */ +#define aligned_u64 unsigned long long __attribute__((aligned(8))) +#endif +#include +#include +#include +#include +#include +#include + +/* should be added to system's socket.h... */ +#ifndef SOL_PPPOL2TP +#define SOL_PPPOL2TP 273 +#endif + +const char pppd_version[] = VERSION; + +static int setdevname_pppol2tp(char **argv); + +static int pppol2tp_fd = -1; +static char *pppol2tp_fd_str; +static bool pppol2tp_lns_mode = 0; +static bool pppol2tp_recv_seq = 0; +static bool pppol2tp_send_seq = 0; +static int pppol2tp_debug_mask = 0; +static int pppol2tp_reorder_timeout = 0; +static char pppol2tp_ifname[32] = { 0, }; +int pppol2tp_tunnel_id = 0; +int pppol2tp_session_id = 0; + +static int device_got_set = 0; +struct channel pppol2tp_channel; + +static void (*old_snoop_recv_hook)(unsigned char *p, int len) = NULL; +static void (*old_snoop_send_hook)(unsigned char *p, int len) = NULL; +static void (*old_ip_up_hook)(void) = NULL; +static void (*old_ip_down_hook)(void) = NULL; + +/* Hook provided to allow other plugins to handle ACCM changes */ +void (*pppol2tp_send_accm_hook)(int tunnel_id, int session_id, + uint32_t send_accm, uint32_t recv_accm) = NULL; + +/* Hook provided to allow other plugins to handle IP up/down */ +void (*pppol2tp_ip_updown_hook)(int tunnel_id, int session_id, int up) = NULL; + +static option_t pppol2tp_options[] = { + { "pppol2tp", o_special, &setdevname_pppol2tp, + "FD for PPPoL2TP socket", OPT_DEVNAM | OPT_A2STRVAL, + &pppol2tp_fd_str }, + { "pppol2tp_lns_mode", o_bool, &pppol2tp_lns_mode, + "PPPoL2TP LNS behavior. Default off.", + OPT_PRIO | OPRIO_CFGFILE }, + { "pppol2tp_send_seq", o_bool, &pppol2tp_send_seq, + "PPPoL2TP enable sequence numbers in transmitted data packets. " + "Default off.", + OPT_PRIO | OPRIO_CFGFILE }, + { "pppol2tp_recv_seq", o_bool, &pppol2tp_recv_seq, + "PPPoL2TP enforce sequence numbers in received data packets. " + "Default off.", + OPT_PRIO | OPRIO_CFGFILE }, + { "pppol2tp_reorderto", o_int, &pppol2tp_reorder_timeout, + "PPPoL2TP data packet reorder timeout. Default 0 (no reordering).", + OPT_PRIO }, + { "pppol2tp_debug_mask", o_int, &pppol2tp_debug_mask, + "PPPoL2TP debug mask. Default: no debug.", + OPT_PRIO }, + { "pppol2tp_ifname", o_string, &pppol2tp_ifname, + "Set interface name of PPP interface", + OPT_PRIO | OPT_PRIV | OPT_STATIC, NULL, 16 }, + { "pppol2tp_tunnel_id", o_int, &pppol2tp_tunnel_id, + "PPPoL2TP tunnel_id.", + OPT_PRIO }, + { "pppol2tp_session_id", o_int, &pppol2tp_session_id, + "PPPoL2TP session_id.", + OPT_PRIO }, + { NULL } +}; + +static int setdevname_pppol2tp(char **argv) +{ + union { + char buffer[128]; + struct sockaddr pppol2tp; + } s; + int len = sizeof(s); + char **a; + int tmp; + int tmp_len = sizeof(tmp); + + if (device_got_set) + return 0; + + if (!int_option(*argv, &pppol2tp_fd)) + return 0; + + if(getsockname(pppol2tp_fd, (struct sockaddr *)&s, &len) < 0) { + fatal("Given FD for PPPoL2TP socket invalid (%s)", + strerror(errno)); + } + if(s.pppol2tp.sa_family != AF_PPPOX) { + fatal("Socket of not a PPPoX socket"); + } + + /* Do a test getsockopt() to ensure that the kernel has the necessary + * feature available. + */ + if (getsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_DEBUG, + &tmp, &tmp_len) < 0) { + fatal("PPPoL2TP kernel driver not installed"); + } + + /* Setup option defaults. Compression options are disabled! */ + + modem = 0; + + lcp_allowoptions[0].neg_accompression = 1; + lcp_wantoptions[0].neg_accompression = 0; + + lcp_allowoptions[0].neg_pcompression = 1; + lcp_wantoptions[0].neg_pcompression = 0; + + ccp_allowoptions[0].deflate = 0; + ccp_wantoptions[0].deflate = 0; + + ipcp_allowoptions[0].neg_vj = 0; + ipcp_wantoptions[0].neg_vj = 0; + + ccp_allowoptions[0].bsd_compress = 0; + ccp_wantoptions[0].bsd_compress = 0; + + the_channel = &pppol2tp_channel; + device_got_set = 1; + + return 1; +} + +static int connect_pppol2tp(void) +{ + if(pppol2tp_fd == -1) { + fatal("No PPPoL2TP FD specified"); + } + + return pppol2tp_fd; +} + +static void disconnect_pppol2tp(void) +{ + if (pppol2tp_fd >= 0) { + close(pppol2tp_fd); + pppol2tp_fd = -1; + } +} + +static void send_config_pppol2tp(int mtu, + u_int32_t asyncmap, + int pcomp, + int accomp) +{ + struct ifreq ifr; + int on = 1; + int fd; + char reorderto[16]; + char tid[8]; + char sid[8]; + + if (pppol2tp_ifname[0]) { + struct ifreq ifr; + int fd; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd >= 0) { + memset (&ifr, '\0', sizeof (ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + strlcpy(ifr.ifr_newname, pppol2tp_ifname, + sizeof(ifr.ifr_name)); + ioctl(fd, SIOCSIFNAME, (caddr_t) &ifr); + strlcpy(ifname, pppol2tp_ifname, 32); + if (pppol2tp_debug_mask & PPPOL2TP_MSG_CONTROL) { + dbglog("ppp%d: interface name %s", + ifunit, ifname); + } + } + close(fd); + } + + if ((lcp_allowoptions[0].mru > 0) && (mtu > lcp_allowoptions[0].mru)) { + warn("Overriding mtu %d to %d", mtu, lcp_allowoptions[0].mru); + mtu = lcp_allowoptions[0].mru; + } + netif_set_mtu(ifunit, mtu); + + reorderto[0] = '\0'; + if (pppol2tp_reorder_timeout > 0) + sprintf(&reorderto[0], "%d ", pppol2tp_reorder_timeout); + tid[0] = '\0'; + if (pppol2tp_tunnel_id > 0) + sprintf(&tid[0], "%hu ", pppol2tp_tunnel_id); + sid[0] = '\0'; + if (pppol2tp_session_id > 0) + sprintf(&sid[0], "%hu ", pppol2tp_session_id); + + dbglog("PPPoL2TP options: %s%s%s%s%s%s%s%s%sdebugmask %d", + pppol2tp_recv_seq ? "recvseq " : "", + pppol2tp_send_seq ? "sendseq " : "", + pppol2tp_lns_mode ? "lnsmode " : "", + pppol2tp_reorder_timeout ? "reorderto " : "", reorderto, + pppol2tp_tunnel_id ? "tid " : "", tid, + pppol2tp_session_id ? "sid " : "", sid, + pppol2tp_debug_mask); + + if (pppol2tp_recv_seq) + if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_RECVSEQ, + &on, sizeof(on)) < 0) + fatal("setsockopt(PPPOL2TP_RECVSEQ): %m"); + if (pppol2tp_send_seq) + if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_SENDSEQ, + &on, sizeof(on)) < 0) + fatal("setsockopt(PPPOL2TP_SENDSEQ): %m"); + if (pppol2tp_lns_mode) + if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_LNSMODE, + &on, sizeof(on)) < 0) + fatal("setsockopt(PPPOL2TP_LNSMODE): %m"); + if (pppol2tp_reorder_timeout) + if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_REORDERTO, + &pppol2tp_reorder_timeout, + sizeof(pppol2tp_reorder_timeout)) < 0) + fatal("setsockopt(PPPOL2TP_REORDERTO): %m"); + if (pppol2tp_debug_mask) + if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_DEBUG, + &pppol2tp_debug_mask, sizeof(pppol2tp_debug_mask)) < 0) + fatal("setsockopt(PPPOL2TP_DEBUG): %m"); +} + +static void recv_config_pppol2tp(int mru, + u_int32_t asyncmap, + int pcomp, + int accomp) +{ + if ((lcp_allowoptions[0].mru > 0) && (mru > lcp_allowoptions[0].mru)) { + warn("Overriding mru %d to mtu value %d", mru, + lcp_allowoptions[0].mru); + mru = lcp_allowoptions[0].mru; + } + if ((ifunit >= 0) && ioctl(pppol2tp_fd, PPPIOCSMRU, (caddr_t) &mru) < 0) + error("Couldn't set PPP MRU: %m"); +} + +/***************************************************************************** + * Snoop LCP message exchanges to capture negotiated ACCM values. + * When asyncmap values have been seen from both sides, give the values to + * L2TP. + * This code is derived from Roaring Penguin L2TP. + *****************************************************************************/ + +static void pppol2tp_lcp_snoop(unsigned char *buf, int len, int incoming) +{ + static bool got_send_accm = 0; + static bool got_recv_accm = 0; + static uint32_t recv_accm = 0xffffffff; + static uint32_t send_accm = 0xffffffff; + static bool snooping = 1; + + uint16_t protocol; + uint16_t lcp_pkt_len; + int opt, opt_len; + int reject; + unsigned char const *opt_data; + uint32_t accm; + + /* Skip HDLC header */ + buf += 2; + len -= 2; + + /* Unreasonably short frame?? */ + if (len <= 0) return; + + /* Get protocol */ + if (buf[0] & 0x01) { + /* Compressed protcol field */ + protocol = buf[0]; + } else { + protocol = ((unsigned int) buf[0]) * 256 + buf[1]; + } + + /* If it's a network protocol, stop snooping */ + if (protocol <= 0x3fff) { + if (pppol2tp_debug_mask & PPPOL2TP_MSG_DEBUG) { + dbglog("Turning off snooping: " + "Network protocol %04x found.", + protocol); + } + snooping = 0; + return; + } + + /* If it's not LCP, do not snoop */ + if (protocol != 0xc021) { + return; + } + + /* Skip protocol; go to packet data */ + buf += 2; + len -= 2; + + /* Unreasonably short frame?? */ + if (len <= 0) return; + + /* Look for Configure-Ack or Configure-Reject code */ + if (buf[0] != CONFACK && buf[0] != CONFREJ) return; + + reject = (buf[0] == CONFREJ); + + lcp_pkt_len = ((unsigned int) buf[2]) * 256 + buf[3]; + + /* Something fishy with length field? */ + if (lcp_pkt_len > len) return; + + /* Skip to options */ + len = lcp_pkt_len - 4; + buf += 4; + + while (len > 0) { + /* Pull off an option */ + opt = buf[0]; + opt_len = buf[1]; + opt_data = &buf[2]; + if (opt_len > len || opt_len < 2) break; + len -= opt_len; + buf += opt_len; + if (pppol2tp_debug_mask & PPPOL2TP_MSG_DEBUG) { + dbglog("Found option type %02x; len %d", opt, opt_len); + } + + /* We are specifically interested in ACCM */ + if (opt == CI_ASYNCMAP && opt_len == 0x06) { + if (reject) { + /* ACCM negotiation REJECTED; use default */ + accm = 0xffffffff; + if (pppol2tp_debug_mask & PPPOL2TP_MSG_DATA) { + dbglog("Rejected ACCM negotiation; " + "defaulting (%s)", + incoming ? "incoming" : "outgoing"); + } + recv_accm = accm; + send_accm = accm; + got_recv_accm = 1; + got_send_accm = 1; + } else { + memcpy(&accm, opt_data, sizeof(accm)); + if (pppol2tp_debug_mask & PPPOL2TP_MSG_DATA) { + dbglog("Found ACCM of %08x (%s)", accm, + incoming ? "incoming" : "outgoing"); + } + if (incoming) { + recv_accm = accm; + got_recv_accm = 1; + } else { + send_accm = accm; + got_send_accm = 1; + } + } + + if (got_recv_accm && got_send_accm) { + if (pppol2tp_debug_mask & PPPOL2TP_MSG_CONTROL) { + dbglog("Telling L2TP: Send ACCM = %08x; " + "Receive ACCM = %08x", send_accm, recv_accm); + } + if (pppol2tp_send_accm_hook != NULL) { + (*pppol2tp_send_accm_hook)(pppol2tp_tunnel_id, + pppol2tp_session_id, + send_accm, recv_accm); + } + got_recv_accm = 0; + got_send_accm = 0; + } + } + } +} + +static void pppol2tp_lcp_snoop_recv(unsigned char *p, int len) +{ + if (old_snoop_recv_hook != NULL) + (*old_snoop_recv_hook)(p, len); + pppol2tp_lcp_snoop(p, len, 1); +} + +static void pppol2tp_lcp_snoop_send(unsigned char *p, int len) +{ + if (old_snoop_send_hook != NULL) + (*old_snoop_send_hook)(p, len); + pppol2tp_lcp_snoop(p, len, 0); +} + +/***************************************************************************** + * Interface up/down events + *****************************************************************************/ + +static void pppol2tp_ip_up_hook(void) +{ + if (old_ip_up_hook != NULL) + (*old_ip_up_hook)(); + + if (pppol2tp_ip_updown_hook != NULL) { + (*pppol2tp_ip_updown_hook)(pppol2tp_tunnel_id, + pppol2tp_session_id, 1); + } +} + +static void pppol2tp_ip_down_hook(void) +{ + if (old_ip_down_hook != NULL) + (*old_ip_down_hook)(); + + if (pppol2tp_ip_updown_hook != NULL) { + (*pppol2tp_ip_updown_hook)(pppol2tp_tunnel_id, + pppol2tp_session_id, 0); + } +} + +/***************************************************************************** + * Application init + *****************************************************************************/ + +static void pppol2tp_check_options(void) +{ + /* Enable LCP snooping for ACCM options only for LNS */ + if (pppol2tp_lns_mode) { + if ((pppol2tp_tunnel_id == 0) || (pppol2tp_session_id == 0)) { + fatal("tunnel_id/session_id values not specified"); + } + if (pppol2tp_debug_mask & PPPOL2TP_MSG_CONTROL) { + dbglog("Enabling LCP snooping"); + } + old_snoop_recv_hook = snoop_recv_hook; + old_snoop_send_hook = snoop_send_hook; + + snoop_recv_hook = pppol2tp_lcp_snoop_recv; + snoop_send_hook = pppol2tp_lcp_snoop_send; + } + + /* Hook up ip up/down hooks to send indicator to openl2tpd + * that the link is up + */ + old_ip_up_hook = ip_up_hook; + ip_up_hook = pppol2tp_ip_up_hook; + old_ip_down_hook = ip_down_hook; + ip_down_hook = pppol2tp_ip_down_hook; +} + +/* Called just before pppd exits. + */ +static void pppol2tp_cleanup(void) +{ + if (pppol2tp_debug_mask & PPPOL2TP_MSG_DEBUG) { + dbglog("pppol2tp: exiting."); + } + disconnect_pppol2tp(); +} + +void plugin_init(void) +{ +#if defined(__linux__) + extern int new_style_driver; /* From sys-linux.c */ + if (!ppp_available() && !new_style_driver) + fatal("Kernel doesn't support ppp_generic - " + "needed for PPPoL2TP"); +#else + fatal("No PPPoL2TP support on this OS"); +#endif + add_options(pppol2tp_options); +} + +struct channel pppol2tp_channel = { + options: pppol2tp_options, + process_extra_options: NULL, + check_options: &pppol2tp_check_options, + connect: &connect_pppol2tp, + disconnect: &disconnect_pppol2tp, + establish_ppp: &generic_establish_ppp, + disestablish_ppp: &generic_disestablish_ppp, + send_config: &send_config_pppol2tp, + recv_config: &recv_config_pppol2tp, + close: NULL, + cleanup: NULL +};