/* 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; /* 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"); } pppol2tp_fd_str = strdup(*argv); if (pppol2tp_fd_str == NULL) novm("PPPoL2TP FD"); /* 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(void *opaque, int arg) { /* may get called twice (for IPv4 and IPv6) but the hook handles that well */ if (pppol2tp_ip_updown_hook != NULL) { (*pppol2tp_ip_updown_hook)(pppol2tp_tunnel_id, pppol2tp_session_id, 1); } } static void pppol2tp_ip_down(void *opaque, int arg) { /* may get called twice (for IPv4 and IPv6) but the hook handles that well */ 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; } } /* 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); /* Hook up ip up/down notifiers to send indicator to openl2tpd * that the link is up */ add_notifier(&ip_up_notifier, pppol2tp_ip_up, NULL); add_notifier(&ip_down_notifier, pppol2tp_ip_down, NULL); add_notifier(&ipv6_up_notifier, pppol2tp_ip_up, NULL); add_notifier(&ipv6_down_notifier, pppol2tp_ip_down, NULL); } 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 };