| /* 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 <kleptog@svana.org> |
| * 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 <unistd.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include "pppd.h" |
| #include "pathnames.h" |
| #include "fsm.h" |
| #include "lcp.h" |
| #include "ccp.h" |
| #include "ipcp.h" |
| #include <sys/stat.h> |
| #include <net/if.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <signal.h> |
| #include <linux/version.h> |
| #include <linux/sockios.h> |
| #ifndef aligned_u64 |
| /* should be defined in sys/types.h */ |
| #define aligned_u64 unsigned long long __attribute__((aligned(8))) |
| #endif |
| #include <linux/types.h> |
| #include <linux/if_ether.h> |
| #include <linux/ppp_defs.h> |
| #include <linux/if_ppp.h> |
| #include <linux/if_pppox.h> |
| #include <linux/if_pppol2tp.h> |
| |
| /* 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 |
| }; |