blob: 83978f03a2bc4929506aec5e2a3b57bed7df44f3
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@styx.uwaterloo.ca>, |
14 | * Jamal Hadi Salim <hadi@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 | * LIC: GPL |
24 | * |
25 | ***********************************************************************/ |
26 | |
27 | static char const RCSID[] = |
28 | "$Id$"; |
29 | |
30 | #define _GNU_SOURCE 1 |
31 | #include "pppoe.h" |
32 | |
33 | #include "pppd/pppd.h" |
34 | #include "pppd/fsm.h" |
35 | #include "pppd/lcp.h" |
36 | #include "pppd/ipcp.h" |
37 | #include "pppd/ccp.h" |
38 | /* #include "pppd/pathnames.h" */ |
39 | |
40 | #include <linux/types.h> |
41 | #include <syslog.h> |
42 | #include <sys/ioctl.h> |
43 | #include <sys/types.h> |
44 | #include <sys/socket.h> |
45 | #include <sys/stat.h> |
46 | #include <string.h> |
47 | #include <stdlib.h> |
48 | #include <errno.h> |
49 | #include <unistd.h> |
50 | #include <fcntl.h> |
51 | #include <signal.h> |
52 | #include <net/ethernet.h> |
53 | #include <net/if_arp.h> |
54 | #include <linux/ppp_defs.h> |
55 | #include <linux/if_pppox.h> |
56 | |
57 | #ifndef _ROOT_PATH |
58 | #define _ROOT_PATH "" |
59 | #endif |
60 | |
61 | #define _PATH_ETHOPT _ROOT_PATH "/etc/ppp/options." |
62 | |
63 | char pppd_version[] = VERSION; |
64 | |
65 | /* From sys-linux.c in pppd -- MUST FIX THIS! */ |
66 | extern int new_style_driver; |
67 | |
68 | char *pppd_pppoe_service = NULL; |
69 | static char *acName = NULL; |
70 | static char *existingSession = NULL; |
71 | static int printACNames = 0; |
72 | |
73 | static int PPPoEDevnameHook(char *cmd, char **argv, int doit); |
74 | static option_t Options[] = { |
75 | { "device name", o_wild, (void *) &PPPoEDevnameHook, |
76 | "PPPoE device name", |
77 | OPT_DEVNAM | OPT_PRIVFIX | OPT_NOARG | OPT_A2STRVAL | OPT_STATIC, |
78 | devnam}, |
79 | { "rp_pppoe_service", o_string, &pppd_pppoe_service, |
80 | "Desired PPPoE service name" }, |
81 | { "rp_pppoe_ac", o_string, &acName, |
82 | "Desired PPPoE access concentrator name" }, |
83 | { "rp_pppoe_sess", o_string, &existingSession, |
84 | "Attach to existing session (sessid:macaddr)" }, |
85 | { "rp_pppoe_verbose", o_int, &printACNames, |
86 | "Be verbose about discovered access concentrators"}, |
87 | { NULL } |
88 | }; |
89 | int (*OldDevnameHook)(char *cmd, char **argv, int doit) = NULL; |
90 | static PPPoEConnection *conn = NULL; |
91 | |
92 | /********************************************************************** |
93 | * %FUNCTION: PPPOEInitDevice |
94 | * %ARGUMENTS: |
95 | * None |
96 | * %RETURNS: |
97 | * |
98 | * %DESCRIPTION: |
99 | * Initializes PPPoE device. |
100 | ***********************************************************************/ |
101 | static int |
102 | PPPOEInitDevice(void) |
103 | { |
104 | conn = malloc(sizeof(PPPoEConnection)); |
105 | if (!conn) { |
106 | fatal("Could not allocate memory for PPPoE session"); |
107 | } |
108 | memset(conn, 0, sizeof(PPPoEConnection)); |
109 | if (acName) { |
110 | SET_STRING(conn->acName, acName); |
111 | } |
112 | if (pppd_pppoe_service) { |
113 | SET_STRING(conn->serviceName, pppd_pppoe_service); |
114 | } |
115 | SET_STRING(conn->ifName, devnam); |
116 | conn->discoverySocket = -1; |
117 | conn->sessionSocket = -1; |
118 | conn->useHostUniq = 1; |
119 | conn->printACNames = printACNames; |
120 | conn->discoveryTimeout = PADI_TIMEOUT; |
121 | return 1; |
122 | } |
123 | |
124 | /********************************************************************** |
125 | * %FUNCTION: PPPOEConnectDevice |
126 | * %ARGUMENTS: |
127 | * None |
128 | * %RETURNS: |
129 | * Non-negative if all goes well; -1 otherwise |
130 | * %DESCRIPTION: |
131 | * Connects PPPoE device. |
132 | ***********************************************************************/ |
133 | static int |
134 | PPPOEConnectDevice(void) |
135 | { |
136 | struct sockaddr_pppox sp; |
137 | |
138 | /* Open session socket before discovery phase, to avoid losing session */ |
139 | /* packets sent by peer just after PADS packet (noted on some Cisco */ |
140 | /* server equipment). */ |
141 | /* Opening this socket just before waitForPADS in the discovery() */ |
142 | /* function would be more appropriate, but it would mess-up the code */ |
143 | conn->sessionSocket = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_OE); |
144 | if (conn->sessionSocket < 0) { |
145 | error("Failed to create PPPoE socket: %m"); |
146 | return -1; |
147 | } |
148 | |
149 | strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam)); |
150 | if (existingSession) { |
151 | unsigned int mac[ETH_ALEN]; |
152 | int i, ses; |
153 | if (sscanf(existingSession, "%d:%x:%x:%x:%x:%x:%x", |
154 | &ses, &mac[0], &mac[1], &mac[2], |
155 | &mac[3], &mac[4], &mac[5]) != 7) { |
156 | fatal("Illegal value for rp_pppoe_sess option"); |
157 | } |
158 | conn->session = htons(ses); |
159 | for (i=0; i<ETH_ALEN; i++) { |
160 | conn->peerEth[i] = (unsigned char) mac[i]; |
161 | } |
162 | } else { |
163 | conn->discoverySocket = |
164 | openInterface(conn->ifName, Eth_PPPOE_Discovery, conn->myEth); |
165 | discovery(conn); |
166 | if (conn->discoveryState != STATE_SESSION) { |
167 | error("Unable to complete PPPoE Discovery"); |
168 | return -1; |
169 | } |
170 | } |
171 | |
172 | /* Set PPPoE session-number for further consumption */ |
173 | ppp_session_number = ntohs(conn->session); |
174 | |
175 | sp.sa_family = AF_PPPOX; |
176 | sp.sa_protocol = PX_PROTO_OE; |
177 | sp.sa_addr.pppoe.sid = conn->session; |
178 | memcpy(sp.sa_addr.pppoe.dev, conn->ifName, IFNAMSIZ); |
179 | memcpy(sp.sa_addr.pppoe.remote, conn->peerEth, ETH_ALEN); |
180 | |
181 | /* Set remote_number for ServPoET */ |
182 | sprintf(remote_number, "%02X:%02X:%02X:%02X:%02X:%02X", |
183 | (unsigned) conn->peerEth[0], |
184 | (unsigned) conn->peerEth[1], |
185 | (unsigned) conn->peerEth[2], |
186 | (unsigned) conn->peerEth[3], |
187 | (unsigned) conn->peerEth[4], |
188 | (unsigned) conn->peerEth[5]); |
189 | |
190 | warn("Connected to %02X:%02X:%02X:%02X:%02X:%02X via interface %s", |
191 | (unsigned) conn->peerEth[0], |
192 | (unsigned) conn->peerEth[1], |
193 | (unsigned) conn->peerEth[2], |
194 | (unsigned) conn->peerEth[3], |
195 | (unsigned) conn->peerEth[4], |
196 | (unsigned) conn->peerEth[5], |
197 | conn->ifName); |
198 | |
199 | script_setenv("MACREMOTE", remote_number, 0); |
200 | |
201 | if (connect(conn->sessionSocket, (struct sockaddr *) &sp, |
202 | sizeof(struct sockaddr_pppox)) < 0) { |
203 | error("Failed to connect PPPoE socket: %d %m", errno); |
204 | return -1; |
205 | } |
206 | |
207 | return conn->sessionSocket; |
208 | } |
209 | |
210 | static void |
211 | PPPOESendConfig(int mtu, |
212 | u_int32_t asyncmap, |
213 | int pcomp, |
214 | int accomp) |
215 | { |
216 | int sock; |
217 | struct ifreq ifr; |
218 | |
219 | if (mtu > MAX_PPPOE_MTU) { |
220 | warn("Couldn't increase MTU to %d", mtu); |
221 | mtu = MAX_PPPOE_MTU; |
222 | } |
223 | sock = socket(AF_INET, SOCK_DGRAM, 0); |
224 | if (sock < 0) { |
225 | warn("Couldn't create IP socket: %m"); |
226 | return; |
227 | } |
228 | strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); |
229 | ifr.ifr_mtu = mtu; |
230 | if (ioctl(sock, SIOCSIFMTU, &ifr) < 0) { |
231 | warn("ioctl(SIOCSIFMTU): %m"); |
232 | return; |
233 | } |
234 | (void) close (sock); |
235 | } |
236 | |
237 | |
238 | static void |
239 | PPPOERecvConfig(int mru, |
240 | u_int32_t asyncmap, |
241 | int pcomp, |
242 | int accomp) |
243 | { |
244 | if (mru > MAX_PPPOE_MTU) { |
245 | warn("Couldn't increase MRU to %d", mru); |
246 | } |
247 | } |
248 | |
249 | /********************************************************************** |
250 | * %FUNCTION: PPPOEDisconnectDevice |
251 | * %ARGUMENTS: |
252 | * None |
253 | * %RETURNS: |
254 | * Nothing |
255 | * %DESCRIPTION: |
256 | * Disconnects PPPoE device |
257 | ***********************************************************************/ |
258 | static void |
259 | PPPOEDisconnectDevice(void) |
260 | { |
261 | struct sockaddr_pppox sp; |
262 | |
263 | sp.sa_family = AF_PPPOX; |
264 | sp.sa_protocol = PX_PROTO_OE; |
265 | sp.sa_addr.pppoe.sid = 0; |
266 | memcpy(sp.sa_addr.pppoe.dev, conn->ifName, IFNAMSIZ); |
267 | memcpy(sp.sa_addr.pppoe.remote, conn->peerEth, ETH_ALEN); |
268 | if (connect(conn->sessionSocket, (struct sockaddr *) &sp, |
269 | sizeof(struct sockaddr_pppox)) < 0) { |
270 | fatal("Failed to disconnect PPPoE socket: %d %m", errno); |
271 | return; |
272 | } |
273 | close(conn->sessionSocket); |
274 | close(conn->discoverySocket); |
275 | |
276 | } |
277 | |
278 | static void |
279 | PPPOEDeviceOptions(void) |
280 | { |
281 | char buf[256]; |
282 | snprintf(buf, 256, _PATH_ETHOPT "%s",devnam); |
283 | if(!options_from_file(buf, 0, 0, 1)) |
284 | exit(EXIT_OPTION_ERROR); |
285 | |
286 | } |
287 | |
288 | struct channel pppoe_channel; |
289 | |
290 | /********************************************************************** |
291 | * %FUNCTION: PPPoEDevnameHook |
292 | * %ARGUMENTS: |
293 | * cmd -- the command (actually, the device name |
294 | * argv -- argument vector |
295 | * doit -- if non-zero, set device name. Otherwise, just check if possible |
296 | * %RETURNS: |
297 | * 1 if we will handle this device; 0 otherwise. |
298 | * %DESCRIPTION: |
299 | * Checks if name is a valid interface name; if so, returns 1. Also |
300 | * sets up devnam (string representation of device). |
301 | ***********************************************************************/ |
302 | static int |
303 | PPPoEDevnameHook(char *cmd, char **argv, int doit) |
304 | { |
305 | int r = 1; |
306 | int fd; |
307 | struct ifreq ifr; |
308 | |
309 | /* Only do it if name is "ethXXX" or "brXXX" or what was specified |
310 | by rp_pppoe_dev option (ugh). */ |
311 | /* Can also specify nic-XXXX in which case the nic- is stripped off. */ |
312 | if (!strncmp(cmd, "nic-", 4)) { |
313 | cmd += 4; |
314 | } else { |
315 | if (strncmp(cmd, "eth", 3) && |
316 | strncmp(cmd, "br", 2)) { |
317 | if (OldDevnameHook) return OldDevnameHook(cmd, argv, doit); |
318 | return 0; |
319 | } |
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, IFNAMSIZ); |
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 | error("Interface %s not Ethernet", cmd); |
338 | r=0; |
339 | } |
340 | } |
341 | } |
342 | } |
343 | |
344 | /* Close socket */ |
345 | close(fd); |
346 | if (r) { |
347 | strncpy(devnam, cmd, sizeof(devnam)); |
348 | if (the_channel != &pppoe_channel) { |
349 | |
350 | the_channel = &pppoe_channel; |
351 | modem = 0; |
352 | |
353 | lcp_allowoptions[0].neg_accompression = 0; |
354 | lcp_wantoptions[0].neg_accompression = 0; |
355 | |
356 | lcp_allowoptions[0].neg_asyncmap = 0; |
357 | lcp_wantoptions[0].neg_asyncmap = 0; |
358 | |
359 | lcp_allowoptions[0].neg_pcompression = 0; |
360 | lcp_wantoptions[0].neg_pcompression = 0; |
361 | |
362 | ipcp_allowoptions[0].neg_vj=0; |
363 | ipcp_wantoptions[0].neg_vj=0; |
364 | |
365 | ccp_allowoptions[0].deflate = 0 ; |
366 | ccp_wantoptions[0].deflate = 0 ; |
367 | |
368 | ccp_allowoptions[0].bsd_compress = 0; |
369 | ccp_wantoptions[0].bsd_compress = 0; |
370 | |
371 | |
372 | PPPOEInitDevice(); |
373 | } |
374 | return 1; |
375 | } |
376 | |
377 | if (OldDevnameHook) r = OldDevnameHook(cmd, argv, doit); |
378 | return r; |
379 | } |
380 | |
381 | /********************************************************************** |
382 | * %FUNCTION: plugin_init |
383 | * %ARGUMENTS: |
384 | * None |
385 | * %RETURNS: |
386 | * Nothing |
387 | * %DESCRIPTION: |
388 | * Initializes hooks for pppd plugin |
389 | ***********************************************************************/ |
390 | void |
391 | plugin_init(void) |
392 | { |
393 | if (!ppp_available() && !new_style_driver) { |
394 | fatal("Linux kernel does not support PPPoE -- are you running 2.4.x?"); |
395 | } |
396 | |
397 | add_options(Options); |
398 | |
399 | info("RP-PPPoE plugin version %s compiled against pppd %s", |
400 | RP_VERSION, VERSION); |
401 | } |
402 | |
403 | /********************************************************************** |
404 | *%FUNCTION: fatalSys |
405 | *%ARGUMENTS: |
406 | * str -- error message |
407 | *%RETURNS: |
408 | * Nothing |
409 | *%DESCRIPTION: |
410 | * Prints a message plus the errno value to stderr and syslog and exits. |
411 | ***********************************************************************/ |
412 | void |
413 | fatalSys(char const *str) |
414 | { |
415 | char buf[1024]; |
416 | int i = errno; |
417 | sprintf(buf, "%.256s: %.256s", str, strerror(i)); |
418 | printErr(buf); |
419 | sprintf(buf, "RP-PPPoE: %.256s: %.256s", str, strerror(i)); |
420 | sendPADT(conn, buf); |
421 | exit(1); |
422 | } |
423 | |
424 | /********************************************************************** |
425 | *%FUNCTION: rp_fatal |
426 | *%ARGUMENTS: |
427 | * str -- error message |
428 | *%RETURNS: |
429 | * Nothing |
430 | *%DESCRIPTION: |
431 | * Prints a message to stderr and syslog and exits. |
432 | ***********************************************************************/ |
433 | void |
434 | rp_fatal(char const *str) |
435 | { |
436 | printErr(str); |
437 | sendPADTf(conn, "RP-PPPoE: %.256s", str); |
438 | exit(1); |
439 | } |
440 | |
441 | /********************************************************************** |
442 | *%FUNCTION: sysErr |
443 | *%ARGUMENTS: |
444 | * str -- error message |
445 | *%RETURNS: |
446 | * Nothing |
447 | *%DESCRIPTION: |
448 | * Prints a message plus the errno value to syslog. |
449 | ***********************************************************************/ |
450 | void |
451 | sysErr(char const *str) |
452 | { |
453 | rp_fatal(str); |
454 | } |
455 | |
456 | |
457 | struct channel pppoe_channel = { |
458 | .options = Options, |
459 | .process_extra_options = &PPPOEDeviceOptions, |
460 | .check_options = NULL, |
461 | .connect = &PPPOEConnectDevice, |
462 | .disconnect = &PPPOEDisconnectDevice, |
463 | .establish_ppp = &generic_establish_ppp, |
464 | .disestablish_ppp = &generic_disestablish_ppp, |
465 | .send_config = &PPPOESendConfig, |
466 | .recv_config = &PPPOERecvConfig, |
467 | .close = NULL, |
468 | .cleanup = NULL |
469 | }; |
470 |