Home | History | Annotate | Download | only in rp-pppoe
      1 /***********************************************************************
      2 *
      3 * plugin.c
      4 *
      5 * pppd plugin for kernel-mode PPPoE on Linux
      6 *
      7 * Copyright (C) 2001 by Roaring Penguin Software Inc., Michal Ostrowski
      8 * and Jamal Hadi Salim.
      9 *
     10 * Much code and many ideas derived from pppoe plugin by Michal
     11 * Ostrowski and Jamal Hadi Salim, which carries this copyright:
     12 *
     13 * Copyright 2000 Michal Ostrowski <mostrows (at) styx.uwaterloo.ca>,
     14 *                Jamal Hadi Salim <hadi (at) cyberus.ca>
     15 * Borrows heavily from the PPPoATM plugin by Mitchell Blank Jr.,
     16 * which is based in part on work from Jens Axboe and Paul Mackerras.
     17 *
     18 * This program is free software; you can redistribute it and/or
     19 * modify it under the terms of the GNU General Public License
     20 * as published by the Free Software Foundation; either version
     21 * 2 of the License, or (at your option) any later version.
     22 *
     23 ***********************************************************************/
     24 
     25 static char const RCSID[] =
     26 "$Id: plugin.c,v 1.17 2008/06/15 04:35:50 paulus Exp $";
     27 
     28 #define _GNU_SOURCE 1
     29 #include "pppoe.h"
     30 
     31 #include "pppd/pppd.h"
     32 #include "pppd/fsm.h"
     33 #include "pppd/lcp.h"
     34 #include "pppd/ipcp.h"
     35 #include "pppd/ccp.h"
     36 /* #include "pppd/pathnames.h" */
     37 
     38 #include <linux/types.h>
     39 #include <sys/ioctl.h>
     40 #include <sys/types.h>
     41 #include <sys/socket.h>
     42 #include <sys/stat.h>
     43 #include <string.h>
     44 #include <stdlib.h>
     45 #include <errno.h>
     46 #include <unistd.h>
     47 #include <fcntl.h>
     48 #include <signal.h>
     49 #include <net/ethernet.h>
     50 #include <net/if_arp.h>
     51 #include <linux/ppp_defs.h>
     52 #include <linux/if_pppox.h>
     53 
     54 #ifndef _ROOT_PATH
     55 #define _ROOT_PATH ""
     56 #endif
     57 
     58 #define _PATH_ETHOPT         _ROOT_PATH "/etc/ppp/options."
     59 
     60 char pppd_version[] = VERSION;
     61 
     62 /* From sys-linux.c in pppd -- MUST FIX THIS! */
     63 extern int new_style_driver;
     64 
     65 char *pppd_pppoe_service = NULL;
     66 static char *acName = NULL;
     67 static char *existingSession = NULL;
     68 static int printACNames = 0;
     69 static char *pppoe_reqd_mac = NULL;
     70 unsigned char pppoe_reqd_mac_addr[6];
     71 
     72 static int PPPoEDevnameHook(char *cmd, char **argv, int doit);
     73 static option_t Options[] = {
     74     { "device name", o_wild, (void *) &PPPoEDevnameHook,
     75       "PPPoE device name",
     76       OPT_DEVNAM | OPT_PRIVFIX | OPT_NOARG  | OPT_A2STRVAL | OPT_STATIC,
     77       devnam},
     78     { "rp_pppoe_service", o_string, &pppd_pppoe_service,
     79       "Desired PPPoE service name" },
     80     { "rp_pppoe_ac",      o_string, &acName,
     81       "Desired PPPoE access concentrator name" },
     82     { "rp_pppoe_sess",    o_string, &existingSession,
     83       "Attach to existing session (sessid:macaddr)" },
     84     { "rp_pppoe_verbose", o_int, &printACNames,
     85       "Be verbose about discovered access concentrators"},
     86     { "pppoe-mac", o_string, &pppoe_reqd_mac,
     87       "Only connect to specified MAC address" },
     88     { NULL }
     89 };
     90 int (*OldDevnameHook)(char *cmd, char **argv, int doit) = NULL;
     91 static PPPoEConnection *conn = NULL;
     92 
     93 /**********************************************************************
     94  * %FUNCTION: PPPOEInitDevice
     95  * %ARGUMENTS:
     96  * None
     97  * %RETURNS:
     98  *
     99  * %DESCRIPTION:
    100  * Initializes PPPoE device.
    101  ***********************************************************************/
    102 static int
    103 PPPOEInitDevice(void)
    104 {
    105     conn = malloc(sizeof(PPPoEConnection));
    106     if (!conn) {
    107 	novm("PPPoE session data");
    108     }
    109     memset(conn, 0, sizeof(PPPoEConnection));
    110     conn->ifName = devnam;
    111     conn->discoverySocket = -1;
    112     conn->sessionSocket = -1;
    113     conn->useHostUniq = 1;
    114     conn->printACNames = printACNames;
    115     conn->discoveryTimeout = PADI_TIMEOUT;
    116     return 1;
    117 }
    118 
    119 /**********************************************************************
    120  * %FUNCTION: PPPOEConnectDevice
    121  * %ARGUMENTS:
    122  * None
    123  * %RETURNS:
    124  * Non-negative if all goes well; -1 otherwise
    125  * %DESCRIPTION:
    126  * Connects PPPoE device.
    127  ***********************************************************************/
    128 static int
    129 PPPOEConnectDevice(void)
    130 {
    131     struct sockaddr_pppox sp;
    132     struct ifreq ifr;
    133     int s;
    134 
    135     /* Open session socket before discovery phase, to avoid losing session */
    136     /* packets sent by peer just after PADS packet (noted on some Cisco    */
    137     /* server equipment).                                                  */
    138     /* Opening this socket just before waitForPADS in the discovery()      */
    139     /* function would be more appropriate, but it would mess-up the code   */
    140     conn->sessionSocket = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_OE);
    141     if (conn->sessionSocket < 0) {
    142 	error("Failed to create PPPoE socket: %m");
    143 	return -1;
    144     }
    145 
    146     /* Restore configuration */
    147     lcp_allowoptions[0].mru = conn->mtu;
    148     lcp_wantoptions[0].mru = conn->mru;
    149 
    150     /* Update maximum MRU */
    151     s = socket(AF_INET, SOCK_DGRAM, 0);
    152     if (s < 0) {
    153 	error("Can't get MTU for %s: %m", conn->ifName);
    154 	goto errout;
    155     }
    156     strncpy(ifr.ifr_name, conn->ifName, sizeof(ifr.ifr_name));
    157     if (ioctl(s, SIOCGIFMTU, &ifr) < 0) {
    158 	error("Can't get MTU for %s: %m", conn->ifName);
    159 	close(s);
    160 	goto errout;
    161     }
    162     close(s);
    163 
    164     if (lcp_allowoptions[0].mru > ifr.ifr_mtu - TOTAL_OVERHEAD)
    165 	lcp_allowoptions[0].mru = ifr.ifr_mtu - TOTAL_OVERHEAD;
    166     if (lcp_wantoptions[0].mru > ifr.ifr_mtu - TOTAL_OVERHEAD)
    167 	lcp_wantoptions[0].mru = ifr.ifr_mtu - TOTAL_OVERHEAD;
    168 
    169     conn->acName = acName;
    170     conn->serviceName = pppd_pppoe_service;
    171     strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam));
    172     if (existingSession) {
    173 	unsigned int mac[ETH_ALEN];
    174 	int i, ses;
    175 	if (sscanf(existingSession, "%d:%x:%x:%x:%x:%x:%x",
    176 		   &ses, &mac[0], &mac[1], &mac[2],
    177 		   &mac[3], &mac[4], &mac[5]) != 7) {
    178 	    fatal("Illegal value for rp_pppoe_sess option");
    179 	}
    180 	conn->session = htons(ses);
    181 	for (i=0; i<ETH_ALEN; i++) {
    182 	    conn->peerEth[i] = (unsigned char) mac[i];
    183 	}
    184     } else {
    185 	conn->discoverySocket =
    186             openInterface(conn->ifName, Eth_PPPOE_Discovery, conn->myEth);
    187 	discovery(conn);
    188 	if (conn->discoveryState != STATE_SESSION) {
    189 	    error("Unable to complete PPPoE Discovery");
    190 	    goto errout;
    191 	}
    192     }
    193 
    194     /* Set PPPoE session-number for further consumption */
    195     ppp_session_number = ntohs(conn->session);
    196 
    197     sp.sa_family = AF_PPPOX;
    198     sp.sa_protocol = PX_PROTO_OE;
    199     sp.sa_addr.pppoe.sid = conn->session;
    200     memcpy(sp.sa_addr.pppoe.dev, conn->ifName, IFNAMSIZ);
    201     memcpy(sp.sa_addr.pppoe.remote, conn->peerEth, ETH_ALEN);
    202 
    203     /* Set remote_number for ServPoET */
    204     sprintf(remote_number, "%02X:%02X:%02X:%02X:%02X:%02X",
    205 	    (unsigned) conn->peerEth[0],
    206 	    (unsigned) conn->peerEth[1],
    207 	    (unsigned) conn->peerEth[2],
    208 	    (unsigned) conn->peerEth[3],
    209 	    (unsigned) conn->peerEth[4],
    210 	    (unsigned) conn->peerEth[5]);
    211 
    212     warn("Connected to %02X:%02X:%02X:%02X:%02X:%02X via interface %s",
    213 	 (unsigned) conn->peerEth[0],
    214 	 (unsigned) conn->peerEth[1],
    215 	 (unsigned) conn->peerEth[2],
    216 	 (unsigned) conn->peerEth[3],
    217 	 (unsigned) conn->peerEth[4],
    218 	 (unsigned) conn->peerEth[5],
    219 	 conn->ifName);
    220 
    221     script_setenv("MACREMOTE", remote_number, 0);
    222 
    223     if (connect(conn->sessionSocket, (struct sockaddr *) &sp,
    224 		sizeof(struct sockaddr_pppox)) < 0) {
    225 	error("Failed to connect PPPoE socket: %d %m", errno);
    226 	goto errout;
    227     }
    228 
    229     return conn->sessionSocket;
    230 
    231  errout:
    232     if (conn->discoverySocket >= 0) {
    233 	sendPADT(conn, NULL);
    234 	close(conn->discoverySocket);
    235 	conn->discoverySocket = -1;
    236     }
    237     close(conn->sessionSocket);
    238     return -1;
    239 }
    240 
    241 static void
    242 PPPOERecvConfig(int mru,
    243 		u_int32_t asyncmap,
    244 		int pcomp,
    245 		int accomp)
    246 {
    247 #if 0 /* broken protocol, but no point harrassing the users I guess... */
    248     if (mru > MAX_PPPOE_MTU)
    249 	warn("Couldn't increase MRU to %d", mru);
    250 #endif
    251 }
    252 
    253 /**********************************************************************
    254  * %FUNCTION: PPPOEDisconnectDevice
    255  * %ARGUMENTS:
    256  * None
    257  * %RETURNS:
    258  * Nothing
    259  * %DESCRIPTION:
    260  * Disconnects PPPoE device
    261  ***********************************************************************/
    262 static void
    263 PPPOEDisconnectDevice(void)
    264 {
    265     struct sockaddr_pppox sp;
    266 
    267     sp.sa_family = AF_PPPOX;
    268     sp.sa_protocol = PX_PROTO_OE;
    269     sp.sa_addr.pppoe.sid = 0;
    270     memcpy(sp.sa_addr.pppoe.dev, conn->ifName, IFNAMSIZ);
    271     memcpy(sp.sa_addr.pppoe.remote, conn->peerEth, ETH_ALEN);
    272     if (connect(conn->sessionSocket, (struct sockaddr *) &sp,
    273 		sizeof(struct sockaddr_pppox)) < 0)
    274 	error("Failed to disconnect PPPoE socket: %d %m", errno);
    275     close(conn->sessionSocket);
    276     /* don't send PADT?? */
    277     if (conn->discoverySocket >= 0)
    278 	close(conn->discoverySocket);
    279 }
    280 
    281 static void
    282 PPPOEDeviceOptions(void)
    283 {
    284     char buf[256];
    285     snprintf(buf, 256, _PATH_ETHOPT "%s", devnam);
    286     if (!options_from_file(buf, 0, 0, 1))
    287 	exit(EXIT_OPTION_ERROR);
    288 
    289 }
    290 
    291 struct channel pppoe_channel;
    292 
    293 /**********************************************************************
    294  * %FUNCTION: PPPoEDevnameHook
    295  * %ARGUMENTS:
    296  * cmd -- the command (actually, the device name
    297  * argv -- argument vector
    298  * doit -- if non-zero, set device name.  Otherwise, just check if possible
    299  * %RETURNS:
    300  * 1 if we will handle this device; 0 otherwise.
    301  * %DESCRIPTION:
    302  * Checks if name is a valid interface name; if so, returns 1.  Also
    303  * sets up devnam (string representation of device).
    304  ***********************************************************************/
    305 static int
    306 PPPoEDevnameHook(char *cmd, char **argv, int doit)
    307 {
    308     int r = 1;
    309     int fd;
    310     struct ifreq ifr;
    311 
    312     /*
    313      * Take any otherwise-unrecognized option as a possible device name,
    314      * and test if it is the name of a network interface with a
    315      * hardware address whose sa_family is ARPHRD_ETHER.
    316      */
    317     if (strlen(cmd) > 4 && !strncmp(cmd, "nic-", 4)) {
    318 	/* Strip off "nic-" */
    319 	cmd += 4;
    320     }
    321 
    322     /* Open a socket */
    323     if ((fd = socket(PF_PACKET, SOCK_RAW, 0)) < 0) {
    324 	r = 0;
    325     }
    326 
    327     /* Try getting interface index */
    328     if (r) {
    329 	strncpy(ifr.ifr_name, cmd, sizeof(ifr.ifr_name));
    330 	if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
    331 	    r = 0;
    332 	} else {
    333 	    if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
    334 		r = 0;
    335 	    } else {
    336 		if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
    337 		    if (doit)
    338 			error("Interface %s not Ethernet", cmd);
    339 		    r = 0;
    340 		}
    341 	    }
    342 	}
    343     }
    344 
    345     /* Close socket */
    346     close(fd);
    347     if (r && doit) {
    348 	strncpy(devnam, cmd, sizeof(devnam));
    349 	if (the_channel != &pppoe_channel) {
    350 
    351 	    the_channel = &pppoe_channel;
    352 	    modem = 0;
    353 
    354 	    PPPOEInitDevice();
    355 	}
    356 	return 1;
    357     }
    358 
    359     return r;
    360 }
    361 
    362 /**********************************************************************
    363  * %FUNCTION: plugin_init
    364  * %ARGUMENTS:
    365  * None
    366  * %RETURNS:
    367  * Nothing
    368  * %DESCRIPTION:
    369  * Initializes hooks for pppd plugin
    370  ***********************************************************************/
    371 void
    372 plugin_init(void)
    373 {
    374     if (!ppp_available() && !new_style_driver) {
    375 	fatal("Linux kernel does not support PPPoE -- are you running 2.4.x?");
    376     }
    377 
    378     add_options(Options);
    379 
    380     info("RP-PPPoE plugin version %s compiled against pppd %s",
    381 	 RP_VERSION, VERSION);
    382 }
    383 
    384 void pppoe_check_options(void)
    385 {
    386     unsigned int mac[6];
    387     int i;
    388 
    389     if (pppoe_reqd_mac != NULL) {
    390 	if (sscanf(pppoe_reqd_mac, "%x:%x:%x:%x:%x:%x",
    391 		   &mac[0], &mac[1], &mac[2], &mac[3],
    392 		   &mac[4], &mac[5]) != 6) {
    393 	    option_error("cannot parse pppoe-mac option value");
    394 	    exit(EXIT_OPTION_ERROR);
    395 	}
    396 	for (i = 0; i < 6; ++i)
    397 	    conn->req_peer_mac[i] = mac[i];
    398 	conn->req_peer = 1;
    399     }
    400 
    401     lcp_allowoptions[0].neg_accompression = 0;
    402     lcp_wantoptions[0].neg_accompression = 0;
    403 
    404     lcp_allowoptions[0].neg_asyncmap = 0;
    405     lcp_wantoptions[0].neg_asyncmap = 0;
    406 
    407     lcp_allowoptions[0].neg_pcompression = 0;
    408     lcp_wantoptions[0].neg_pcompression = 0;
    409 
    410     if (lcp_allowoptions[0].mru > MAX_PPPOE_MTU)
    411 	lcp_allowoptions[0].mru = MAX_PPPOE_MTU;
    412     if (lcp_wantoptions[0].mru > MAX_PPPOE_MTU)
    413 	lcp_wantoptions[0].mru = MAX_PPPOE_MTU;
    414 
    415     /* Save configuration */
    416     conn->mtu = lcp_allowoptions[0].mru;
    417     conn->mru = lcp_wantoptions[0].mru;
    418 
    419     ccp_allowoptions[0].deflate = 0;
    420     ccp_wantoptions[0].deflate = 0;
    421 
    422     ipcp_allowoptions[0].neg_vj = 0;
    423     ipcp_wantoptions[0].neg_vj = 0;
    424 
    425     ccp_allowoptions[0].bsd_compress = 0;
    426     ccp_wantoptions[0].bsd_compress = 0;
    427 }
    428 
    429 struct channel pppoe_channel = {
    430     .options = Options,
    431     .process_extra_options = &PPPOEDeviceOptions,
    432     .check_options = pppoe_check_options,
    433     .connect = &PPPOEConnectDevice,
    434     .disconnect = &PPPOEDisconnectDevice,
    435     .establish_ppp = &generic_establish_ppp,
    436     .disestablish_ppp = &generic_disestablish_ppp,
    437     .send_config = NULL,
    438     .recv_config = &PPPOERecvConfig,
    439     .close = NULL,
    440     .cleanup = NULL
    441 };
    442