From 11996ba4147678c1d400dcc16e33d83a9a3ede9c Mon Sep 17 00:00:00 2001 From: fendo Date: Tue, 8 Jan 2013 10:50:57 +0100 Subject: [PATCH] Add PPPOE-SERVER functionality --- Makefile | 15 +- cli.c | 22 +- cluster.c | 46 +- cluster.h | 2 + debian/changelog | 6 + icmp.c | 3 + l2tplac.c | 12 +- l2tpns.c | 126 +++-- l2tpns.h | 28 +- ppp.c | 6 + pppoe.c | 1158 ++++++++++++++++++++++++++++++++++++++++++++++ pppoe.h | 23 + radius.c | 1 + util.c | 11 + util.h | 1 + 15 files changed, 1403 insertions(+), 57 deletions(-) create mode 100644 pppoe.c create mode 100644 pppoe.h diff --git a/Makefile b/Makefile index 6af8491..21221ab 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ INSTALL = install -c -D -o root -g root l2tpns.LIBS = -lm -lcli -ldl OBJS = arp.o cli.o cluster.o constants.o control.o icmp.o l2tpns.o \ - ll.o md5.o ppp.o radius.o tbf.o util.o + ll.o md5.o ppp.o radius.o tbf.o util.o pppoe.o PROGRAMS = l2tpns nsctl PLUGINS = autosnoop.so autothrottle.so garden.so sessionctl.so \ @@ -114,22 +114,23 @@ install: all arp.o: arp.c l2tpns.h cli.o: cli.c l2tpns.h constants.h util.h cluster.h tbf.h ll.h bgp.h \ l2tplac.h -cluster.o: cluster.c l2tpns.h cluster.h util.h tbf.h bgp.h +cluster.o: cluster.c l2tpns.h cluster.h util.h tbf.h pppoe.h bgp.h constants.o: constants.c constants.h control.o: control.c l2tpns.h control.h -icmp.o: icmp.c l2tpns.h +icmp.o: icmp.c l2tpns.h pppoe.h l2tpns.o: l2tpns.c md5.h l2tpns.h cluster.h plugin.h ll.h constants.h \ - control.h util.h tbf.h bgp.h l2tplac.h + control.h util.h tbf.h bgp.h l2tplac.h pppoe.h ll.o: ll.c ll.h md5.o: md5.c md5.h ppp.o: ppp.c l2tpns.h constants.h plugin.h util.h tbf.h cluster.h \ - l2tplac.h + l2tplac.h pppoe.h radius.o: radius.c md5.h constants.h l2tpns.h plugin.h util.h cluster.h \ - l2tplac.h + l2tplac.h pppoe.h tbf.o: tbf.c l2tpns.h util.h tbf.h util.o: util.c l2tpns.h bgp.h +pppoe.o: pppoe.c l2tpns.h cluster.h constants.h md5.h util.h bgp.o: bgp.c l2tpns.h bgp.h util.h -l2tplac.o: l2tplac.c md5.h l2tpns.h util.h cluster.h l2tplac.h +l2tplac.o: l2tplac.c md5.h l2tpns.h util.h cluster.h l2tplac.h pppoe.h autosnoop.so: autosnoop.c l2tpns.h plugin.h autothrottle.so: autothrottle.c l2tpns.h plugin.h garden.so: garden.c l2tpns.h plugin.h control.h diff --git a/cli.c b/cli.c index 09ea768..3f950e0 100644 --- a/cli.c +++ b/cli.c @@ -564,9 +564,9 @@ static int cmd_show_session(struct cli_def *cli, char *command, char **argv, int "idle", "Rem.Time", #ifdef LAC - "LAC(L)/R LNS(R)", + "LAC(L)/RLNS(R)/PPPOE(P)", #else - "LAC", + "LAC(L)/PPPOE(P)", #endif "CLI"); @@ -579,9 +579,9 @@ static int cmd_show_session(struct cli_def *cli, char *command, char **argv, int else rem_time = session[i].timeout ? (session[i].timeout - (time_now-session[i].opened)) : 0; #ifdef LAC - cli_print(cli, "%5d %7d %4d %-32s %-15s %s %s %s %s %10u %10lu %10lu %4u %10lu %3s%-15s %s", + cli_print(cli, "%5d %7d %4d %-32s %-15s %s %s %s %s %10u %10lu %10lu %4u %10lu %3s%-20s %s", #else - cli_print(cli, "%5d %4d %-32s %-15s %s %s %s %s %10u %10lu %10lu %4u %10lu %-15s %s", + cli_print(cli, "%5d %4d %-32s %-15s %s %s %s %s %10u %10lu %10lu %4u %10lu %3s%-20s %s", #endif i, #ifdef LAC @@ -600,9 +600,11 @@ static int cmd_show_session(struct cli_def *cli, char *command, char **argv, int abs(time_now - (session[i].last_packet ? session[i].last_packet : time_now)), (unsigned long)(rem_time), #ifdef LAC - tunnel[session[i].tunnel].isremotelns?"(R)":"(L)", + (session[i].tunnel == TUNNEL_ID_PPPOE)?"(P)":(tunnel[session[i].tunnel].isremotelns?"(R)":"(L)"), +#else + (session[i].tunnel == TUNNEL_ID_PPPOE)?"(P)":"(L)", #endif - fmtaddr(htonl(tunnel[ session[i].tunnel ].ip), 1), + (session[i].tunnel == TUNNEL_ID_PPPOE)?fmtMacAddr(session[i].src_hwaddr):fmtaddr(htonl(tunnel[session[i].tunnel].ip), 1), session[i].calling[0] ? session[i].calling : "*"); } return CLI_OK; @@ -686,18 +688,16 @@ static int cmd_show_tunnels(struct cli_def *cli, char *command, char **argv, int if (!show_all && (!tunnel[i].ip || tunnel[i].die)) continue; for (x = 0; x < MAXSESSION; x++) if (session[x].tunnel == i && session[x].opened && !session[x].die) sessions++; -#ifdef LAC cli_print(cli, "%4d %20s %20s %6s %6d %s", -#else - cli_print(cli, "%4d %20s %20s %6s %6d", -#endif i, *tunnel[i].hostname ? tunnel[i].hostname : "(null)", fmtaddr(htonl(tunnel[i].ip), 0), states[tunnel[i].state], sessions #ifdef LAC - ,(tunnel[i].isremotelns?"Tunnel To Remote LNS":"Tunnel To LAC") + ,(i == TUNNEL_ID_PPPOE)?"Tunnel pppoe":(tunnel[i].isremotelns?"Tunnel To Remote LNS":"Tunnel To LAC") +#else + ,(i == TUNNEL_ID_PPPOE)?"Tunnel pppoe":"Tunnel To LAC" #endif ); } diff --git a/cluster.c b/cluster.c index 9277f41..cf65972 100644 --- a/cluster.c +++ b/cluster.c @@ -21,6 +21,7 @@ #include "cluster.h" #include "util.h" #include "tbf.h" +#include "pppoe.h" #ifdef BGP #include "bgp.h" @@ -303,10 +304,39 @@ static int _forward_packet(uint8_t *data, int size, in_addr_t addr, int port, in // // The master just processes the payload as if it had // received it off the tun device. -// +//(note: THIS ROUTINE WRITES TO pack[-6]). int master_forward_packet(uint8_t *data, int size, in_addr_t addr, int port) { - return _forward_packet(data, size, addr, port, C_FORWARD); + uint8_t *p = data - (3 * sizeof(uint32_t)); + uint8_t *psave = p; + + if (!config->cluster_master_address) // No election has been held yet. Just skip it. + return -1; + + LOG(4, 0, 0, "Forwarding packet from %s to master (size %d)\n", fmtaddr(addr, 0), size); + + STAT(c_forwarded); + add_type(&p, C_FORWARD, addr, (uint8_t *) &port, sizeof(port)); // ick. should be uint16_t + + return peer_send_data(config->cluster_master_address, psave, size + (3 * sizeof(uint32_t))); +} + +// Forward PPPOE packet to the master. +//(note: THIS ROUTINE WRITES TO pack[-4]). +int master_forward_pppoe_packet(uint8_t *data, int size, uint8_t codepad) +{ + uint8_t *p = data - (2 * sizeof(uint32_t)); + uint8_t *psave = p; + + if (!config->cluster_master_address) // No election has been held yet. Just skip it. + return -1; + + LOG(4, 0, 0, "Forward PPPOE packet to master, code %s (size %d)\n", get_string_codepad(codepad), size); + + STAT(c_forwarded); + add_type(&p, C_PPPOE_FORWARD, codepad, NULL, 0); + + return peer_send_data(config->cluster_master_address, psave, size + (2 * sizeof(uint32_t))); } // Forward a DAE RADIUS packet to the master. @@ -606,6 +636,7 @@ void cluster_check_master(void) // to become a master!!! config->cluster_iam_master = 1; + pppoe_send_garp(); // gratuitous arp of the pppoe interface LOG(0, 0, 0, "I am declaring myself the master!\n"); @@ -1871,6 +1902,17 @@ int processcluster(uint8_t *data, int size, in_addr_t addr) return 0; } + case C_PPPOE_FORWARD: + if (!config->cluster_iam_master) + { + LOG(0, 0, 0, "I'm not the master, but I got a C_PPPOE_FORWARD from %s?\n", fmtaddr(addr, 0)); + return -1; + } + else + { + pppoe_process_forward(p, s, addr); + return 0; + } case C_MPPP_FORWARD: // Receive a MPPP packet from a slave. diff --git a/cluster.h b/cluster.h index 0581647..59a6aa6 100644 --- a/cluster.h +++ b/cluster.h @@ -24,6 +24,7 @@ #define C_BUNDLE 17 // Bundle structure. #define C_CBUNDLE 18 // Compressed bundle structure. #define C_MPPP_FORWARD 19 // MPPP Forwarded packet.. +#define C_PPPOE_FORWARD 20 // PPPOE Forwarded packet.. #ifdef LAC #define HB_VERSION 7 // Protocol version number.. @@ -97,5 +98,6 @@ void cluster_heartbeat(void); void cluster_check_master(void); void cluster_check_slaves(void); int cmd_show_cluster(struct cli_def *cli, char *command, char **argv, int argc); +int master_forward_pppoe_packet(uint8_t *data, int size, uint8_t codepad); #endif /* __CLUSTER_H__ */ diff --git a/debian/changelog b/debian/changelog index a9aa82b..2c8c543 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +l2tpns (2.2.1-2fdn3.2-pppoe) unstable; urgency=low + + * pppoe server functionality + + -- Fernando Alves Thu, 27 Dec 2012 22:54:02 +0100 + l2tpns (2.2.1-2fdn3.2) unstable; urgency=low * Authorize to change the source IP of the tunnels l2tp. diff --git a/icmp.c b/icmp.c index 6d90467..fa947b7 100644 --- a/icmp.c +++ b/icmp.c @@ -15,6 +15,7 @@ #include #include "l2tpns.h" +#include "pppoe.h" static uint16_t _checksum(uint8_t *addr, int count); @@ -130,7 +131,9 @@ void send_ipv6_ra(sessionidt s, tunnelidt t, struct in6_addr *ip) *(o+9) = 0x80; *(o+23) = 1; if (ip != NULL) + { memcpy(o+24, ip, 16); // dest = ip + } else { // FF02::1 - all hosts diff --git a/l2tplac.c b/l2tplac.c index 281bbc4..6ad4683 100644 --- a/l2tplac.c +++ b/l2tplac.c @@ -1,7 +1,10 @@ /* + * Fernando ALVES 2013 * Add functionality "LAC" to l2tpns. * Used to forward a ppp session to another "LNS". + * GPL licenced */ + #include #include @@ -11,6 +14,7 @@ #include "cluster.h" #include "l2tplac.h" +#include "pppoe.h" /* sequence diagram: Client <--> LAC <--> LNS1 <--> LNS2 * @@ -466,7 +470,7 @@ int lac_session_forward(uint8_t *buf, int len, sessionidt sess, uint16_t proto, if ((!tunnel[t].isremotelns) && (!tunnel[session[sess].tunnel].isremotelns)) { - LOG(0, sess, session[sess].tunnel, "Link Tunnel Session (%u) broken\n", s); + LOG(0, sess, session[sess].tunnel, "Link Tunnel Session (%u/%u) broken\n", s, t); return 0; } @@ -483,6 +487,12 @@ int lac_session_forward(uint8_t *buf, int len, sessionidt sess, uint16_t proto, } } + if (t == TUNNEL_ID_PPPOE) + { + pppoe_forwardto_session_pppoe(buf, len, sess, proto); + return 1; + } + if (*buf & 0x40) { // length p += 2; diff --git a/l2tpns.c b/l2tpns.c index 9db5bc3..71d5857 100644 --- a/l2tpns.c +++ b/l2tpns.c @@ -56,6 +56,7 @@ #ifdef LAC #include "l2tplac.h" #endif +#include "pppoe.h" #ifdef LAC char * Vendor_name = "Linux L2TPNS"; @@ -186,6 +187,9 @@ config_descriptt config_values[] = { CONFIG("bind_address_remotelns", bind_address_remotelns, IPv4), CONFIG("bind_portremotelns", bind_portremotelns, SHORT), #endif + CONFIG("pppoe_if_name", pppoe_if_name, STRING), + CONFIG("pppoe_service_name", pppoe_service_name, STRING), + CONFIG("pppoe_ac_name", pppoe_ac_name, STRING), { NULL, 0, 0, 0 }, }; @@ -1195,6 +1199,12 @@ void tunnelsend(uint8_t * buf, uint16_t l, tunnelidt t) return; } + if (t == TUNNEL_ID_PPPOE) + { + pppoe_sess_send(buf, l, t); + return; + } + if (!tunnel[t].ip) { LOG(1, 0, t, "Error sending data out tunnel: no remote endpoint (tunnel not set up)\n"); @@ -2134,20 +2144,28 @@ void sessionshutdown(sessionidt s, char const *reason, int cdn_result, int cdn_e throttle_session(s, 0, 0); if (cdn_result) - { // Send CDN - controlt *c = controlnew(14); // sending CDN - if (cdn_error) + { + if (session[s].tunnel == TUNNEL_ID_PPPOE) { - uint16_t buf[2]; - buf[0] = htons(cdn_result); - buf[1] = htons(cdn_error); - controlb(c, 1, (uint8_t *)buf, 4, 1); + pppoe_shutdown_session(s); } else - control16(c, 1, cdn_result, 1); + { + // Send CDN + controlt *c = controlnew(14); // sending CDN + if (cdn_error) + { + uint16_t buf[2]; + buf[0] = htons(cdn_result); + buf[1] = htons(cdn_error); + controlb(c, 1, (uint8_t *)buf, 4, 1); + } + else + control16(c, 1, cdn_result, 1); - control16(c, 14, s, 1); // assigned session (our end) - controladd(c, session[s].far, session[s].tunnel); // send the message + control16(c, 14, s, 1); // assigned session (our end) + controladd(c, session[s].far, session[s].tunnel); // send the message + } } // update filter refcounts @@ -2414,6 +2432,12 @@ void processudp(uint8_t *buf, int len, struct sockaddr_in *addr) STAT(tunnel_rx_errors); return; } + if (t == TUNNEL_ID_PPPOE) + { + LOG(1, s, t, "Received UDP packet with tunnel ID reserved for pppoe\n"); + STAT(tunnel_rx_errors); + return; + } if (*buf & 0x08) { // ns/nr ns = ntohs(*(uint16_t *) p); @@ -3441,6 +3465,9 @@ static void regular_cleanups(double period) if (t > config->cluster_highest_tunnelid) t = 1; + if (t == TUNNEL_ID_PPPOE) + continue; + // check for expired tunnels if (tunnel[t].die && tunnel[t].die <= TIME) { @@ -3664,7 +3691,8 @@ static void regular_cleanups(double period) LOG(4, s, session[s].tunnel, "No data in %d seconds, sending LCP ECHO\n", (int)(time_now - session[s].last_packet)); - tunnelsend(b, 24, session[s].tunnel); // send it + + tunnelsend(b, (q - b) + 8, session[s].tunnel); // send it sess_local[s].last_echo = time_now; s_actions++; } @@ -3916,11 +3944,11 @@ static int still_busy(void) #endif #ifdef LAC -// the base set of fds polled: cli, cluster, tun, udp, control, dae, netlink, udplac -#define BASE_FDS 8 +// the base set of fds polled: cli, cluster, tun, udp, control, dae, netlink, udplac, pppoedisc, pppoesess +#define BASE_FDS 10 #else -// the base set of fds polled: cli, cluster, tun, udp, control, dae, netlink -#define BASE_FDS 7 +// the base set of fds polled: cli, cluster, tun, udp, control, dae, netlink, pppoedisc, pppoesess +#define BASE_FDS 9 #endif // additional polled fds @@ -3935,8 +3963,9 @@ static void mainloop(void) { int i; uint8_t buf[65536]; - uint8_t *p = buf + 8; // for the hearder of the forwarded MPPP packet (see C_MPPP_FORWARD) - int size_bufp = sizeof(buf) - 8; + uint8_t *p = buf + 24; // for the hearder of the forwarded MPPP packet (see C_MPPP_FORWARD) + // and the forwarded pppoe session + int size_bufp = sizeof(buf) - 24; clockt next_cluster_ping = 0; // send initial ping immediately struct epoll_event events[BASE_FDS + RADIUS_FDS + EXTRA_FDS]; int maxevent = sizeof(events)/sizeof(*events); @@ -3948,11 +3977,11 @@ static void mainloop(void) } #ifdef LAC - LOG(4, 0, 0, "Beginning of main loop. clifd=%d, cluster_sockfd=%d, tunfd=%d, udpfd=%d, controlfd=%d, daefd=%d, nlfd=%d , udplacfd=%d\n", - clifd, cluster_sockfd, tunfd, udpfd, controlfd, daefd, nlfd, udplacfd); + LOG(4, 0, 0, "Beginning of main loop. clifd=%d, cluster_sockfd=%d, tunfd=%d, udpfd=%d, controlfd=%d, daefd=%d, nlfd=%d , udplacfd=%d, pppoefd=%d, pppoesessfd=%d\n", + clifd, cluster_sockfd, tunfd, udpfd, controlfd, daefd, nlfd, udplacfd, pppoediscfd, pppoesessfd); #else - LOG(4, 0, 0, "Beginning of main loop. clifd=%d, cluster_sockfd=%d, tunfd=%d, udpfd=%d, controlfd=%d, daefd=%d, nlfd=%d\n", - clifd, cluster_sockfd, tunfd, udpfd, controlfd, daefd, nlfd); + LOG(4, 0, 0, "Beginning of main loop. clifd=%d, cluster_sockfd=%d, tunfd=%d, udpfd=%d, controlfd=%d, daefd=%d, nlfd=%d, pppoefd=%d, pppoesessfd=%d\n", + clifd, cluster_sockfd, tunfd, udpfd, controlfd, daefd, nlfd, pppoediscfd, pppoesessfd); #endif /* setup our fds to poll for input */ @@ -3999,6 +4028,14 @@ static void mainloop(void) e.data.ptr = &d[i++]; epoll_ctl(epollfd, EPOLL_CTL_ADD, udplacfd, &e); #endif + + d[i].type = FD_TYPE_PPPOEDISC; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, pppoediscfd, &e); + + d[i].type = FD_TYPE_PPPOESESS; + e.data.ptr = &d[i++]; + epoll_ctl(epollfd, EPOLL_CTL_ADD, pppoesessfd, &e); } #ifdef BGP @@ -4065,6 +4102,8 @@ static void mainloop(void) int udplac_ready = 0; int udplac_pkts = 0; #endif + int pppoesess_ready = 0; + int pppoesess_pkts = 0; int tun_ready = 0; int cluster_ready = 0; int udp_pkts = 0; @@ -4105,6 +4144,14 @@ static void mainloop(void) #ifdef LAC case FD_TYPE_UDPLAC: udplac_ready++; break; #endif + case FD_TYPE_PPPOESESS: pppoesess_ready++; break; + + case FD_TYPE_PPPOEDISC: // pppoe discovery + s = read(pppoediscfd, p, size_bufp); + if (s > 0) process_pppoe_disc(p, s); + n--; + break; + case FD_TYPE_CONTROL: // nsctl commands alen = sizeof(addr); s = recvfromto(controlfd, buf, sizeof(buf), MSG_WAITALL, (struct sockaddr *) &addr, &alen, &local); @@ -4183,9 +4230,9 @@ static void mainloop(void) if (udp_ready) { alen = sizeof(addr); - if ((s = recvfrom(udpfd, buf, sizeof(buf), 0, (void *) &addr, &alen)) > 0) + if ((s = recvfrom(udpfd, p, size_bufp, 0, (void *) &addr, &alen)) > 0) { - processudp(buf, s, &addr); + processudp(p, s, &addr); udp_pkts++; } else @@ -4199,10 +4246,10 @@ static void mainloop(void) if (udplac_ready) { alen = sizeof(addr); - if ((s = recvfrom(udplacfd, buf, sizeof(buf), 0, (void *) &addr, &alen)) > 0) + if ((s = recvfrom(udplacfd, p, size_bufp, 0, (void *) &addr, &alen)) > 0) { if (!config->disable_lac_func) - processudp(buf, s, &addr); + processudp(p, s, &addr); udplac_pkts++; } @@ -4228,13 +4275,28 @@ static void mainloop(void) } } + // pppoe session + if (pppoesess_ready) + { + if ((s = read(pppoesessfd, p, size_bufp)) > 0) + { + process_pppoe_sess(p, s); + pppoesess_pkts++; + } + else + { + pppoesess_ready = 0; + n--; + } + } + // cluster if (cluster_ready) { alen = sizeof(addr); - if ((s = recvfrom(cluster_sockfd, buf, sizeof(buf), MSG_WAITALL, (void *) &addr, &alen)) > 0) + if ((s = recvfrom(cluster_sockfd, p, size_bufp, MSG_WAITALL, (void *) &addr, &alen)) > 0) { - processcluster(buf, s, addr.sin_addr.s_addr); + processcluster(p, s, addr.sin_addr.s_addr); cluster_pkts++; } else @@ -5059,6 +5121,11 @@ int main(int argc, char *argv[]) inittun(); LOG(1, 0, 0, "Set up on interface %s\n", config->tundevicename); + if (*config->pppoe_if_name) + { + init_pppoe(); + LOG(1, 0, 0, "Set up on pppoe interface %s\n", config->pppoe_if_name); + } initudp(); initrad(); initippool(); @@ -5300,6 +5367,9 @@ static void update_config() if(!config->iftun_address) config->iftun_address = config->bind_address; + if (!*config->pppoe_ac_name) + strncpy(config->pppoe_ac_name, DEFAULT_PPPOE_AC_NAME, sizeof(config->pppoe_ac_name) - 1); + // re-initialise the random number source initrandom(config->random_device); @@ -6036,7 +6106,7 @@ static tunnelidt new_tunnel() tunnelidt i; for (i = 1; i < MAXTUNNEL; i++) { - if (tunnel[i].state == TUNNELFREE) + if ((tunnel[i].state == TUNNELFREE) && (i != TUNNEL_ID_PPPOE)) { LOG(4, 0, i, "Assigning tunnel ID %u\n", i); if (i > config->cluster_highest_tunnelid) diff --git a/l2tpns.h b/l2tpns.h index 5dbaee7..b4ab8e8 100644 --- a/l2tpns.h +++ b/l2tpns.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,9 @@ #define MAXSESSION 60000 // could be up to 65535 #define MAXTBFS 6000 // Maximum token bucket filters. Might need up to 2 * session. +// Tunnel Id reserved for pppoe +#define TUNNEL_ID_PPPOE 1 + #define RADIUS_SHIFT 6 #define RADIUS_FDS (1 << RADIUS_SHIFT) #define RADIUS_MASK ((1 << RADIUS_SHIFT) - 1) @@ -323,9 +327,11 @@ typedef struct struct in6_addr ipv6route; // Static IPv6 route #ifdef LAC sessionidt forwardtosession; // LNS id_session to forward - char reserved[10]; // Space to expand structure without changing HB_VERSION + uint8_t src_hwaddr[ETH_ALEN]; // MAC addr source (for pppoe sessions 6 bytes) + char reserved[4]; // Space to expand structure without changing HB_VERSION #else - char reserved[12]; // Space to expand structure without changing HB_VERSION + uint8_t src_hwaddr[ETH_ALEN]; // MAC addr source (for pppoe sessions 6 bytes) + char reserved[6]; // Space to expand structure without changing HB_VERSION #endif } sessiont; @@ -769,6 +775,10 @@ typedef struct uint16_t bind_portremotelns; in_addr_t bind_address_remotelns; #endif + char pppoe_if_name[IFNAMSIZ]; // Name pppoe interface to bind + char pppoe_service_name[64]; // pppoe service name + char pppoe_ac_name[64]; + uint8_t pppoe_hwaddr[ETH_ALEN]; // MAC addr of interface pppoe to bind } configt; enum config_typet { INT, STRING, UNSIGNED_LONG, SHORT, BOOL, IPv4, IPv6 }; @@ -990,18 +1000,20 @@ extern int epollfd; struct event_data { enum { - FD_TYPE_CLI, - FD_TYPE_CLUSTER, - FD_TYPE_TUN, - FD_TYPE_UDP, - FD_TYPE_CONTROL, - FD_TYPE_DAE, + FD_TYPE_CLI, + FD_TYPE_CLUSTER, + FD_TYPE_TUN, + FD_TYPE_UDP, + FD_TYPE_CONTROL, + FD_TYPE_DAE, FD_TYPE_RADIUS, FD_TYPE_BGP, FD_TYPE_NETLINK, #ifdef LAC FD_TYPE_UDPLAC, #endif + FD_TYPE_PPPOEDISC, + FD_TYPE_PPPOESESS } type; int index; // for RADIUS, BGP }; diff --git a/ppp.c b/ppp.c index 236188e..5ed8563 100644 --- a/ppp.c +++ b/ppp.c @@ -15,6 +15,7 @@ #ifdef LAC #include "l2tplac.h" #endif +#include "pppoe.h" extern tunnelt *tunnel; extern bundlet *bundle; @@ -2521,6 +2522,11 @@ uint8_t *makeppp(uint8_t *b, int size, uint8_t *p, int l, sessionidt s, tunnelid uint16_t type = mtype; uint8_t *start = b; + if (t == TUNNEL_ID_PPPOE) + { + return pppoe_makeppp(b, size, p, l, s, t, mtype, prio, bid, mp_bits); + } + if (size < 16) // Need more space than this!! { LOG(0, s, t, "makeppp buffer too small for L2TP header (size=%d)\n", size); diff --git a/pppoe.c b/pppoe.c new file mode 100644 index 0000000..d00c64f --- /dev/null +++ b/pppoe.c @@ -0,0 +1,1158 @@ +/* + * Fernando ALVES 2013 + * Add functionality "server pppoe" to l2tpns. + * GPL licenced + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l2tpns.h" +#include "cluster.h" +#include "constants.h" +#include "md5.h" +#include "util.h" + +int pppoediscfd = -1; +int pppoesessfd = -1; + +static uint8_t bc_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +/* PPPoE codes */ +#define CODE_PADI 0x09 +#define CODE_PADO 0x07 +#define CODE_PADR 0x19 +#define CODE_PADS 0x65 +#define CODE_PADT 0xA7 +#define CODE_SESS 0x00 + +/* PPPoE Tags */ +#define TAG_END_OF_LIST 0x0000 +#define TAG_SERVICE_NAME 0x0101 +#define TAG_AC_NAME 0x0102 +#define TAG_HOST_UNIQ 0x0103 +#define TAG_AC_COOKIE 0x0104 +#define TAG_VENDOR_SPECIFIC 0x0105 +#define TAG_RELAY_SESSION_ID 0x0110 +#define TAG_SERVICE_NAME_ERROR 0x0201 +#define TAG_AC_SYSTEM_ERROR 0x0202 +#define TAG_GENERIC_ERROR 0x0203 + +static char *code_pad[] = { + "PADI", + "PADO", + "PADR", + "PADS", + "PADT", + "SESS", + NULL +}; + +enum +{ + INDEX_PADI = 0, + INDEX_PADO, + INDEX_PADR, + INDEX_PADS, + INDEX_PADT, + INDEX_SESS +}; + +// set up pppoe discovery socket +static void init_pppoe_disc(void) +{ + int on = 1; + struct ifreq ifr; + struct sockaddr_ll sa; + + memset(&ifr, 0, sizeof(ifr)); + memset(&sa, 0, sizeof(sa)); + + pppoediscfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_PPP_DISC)); + if (pppoediscfd < 0) + { + LOG(0, 0, 0, "Error pppoe: socket: %s\n", strerror(errno)); + exit(1); + } + + fcntl(pppoediscfd, F_SETFD, fcntl(pppoediscfd, F_GETFD) | FD_CLOEXEC); + + if (setsockopt(pppoediscfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))) + { + LOG(0, 0, 0, "Error pppoe: setsockopt(SO_BROADCAST): %s\n", strerror(errno)); + exit(1); + } + + assert(strlen(ifr.ifr_name) < sizeof(config->pppoe_if_name) - 1); + if (*config->pppoe_if_name) + strncpy(ifr.ifr_name, config->pppoe_if_name, IFNAMSIZ); + + if (ioctl(pppoediscfd, SIOCGIFHWADDR, &ifr)) + { + LOG(0, 0, 0, "Error pppoe: ioctl(SIOCGIFHWADDR): %s\n", strerror(errno)); + exit(1); + } + + if ((ifr.ifr_hwaddr.sa_data[0] & 1) != 0) + { + LOG(0, 0, 0, "Error pppoe: interface %s has not unicast address\n", config->pppoe_if_name); + exit(1); + } + + memcpy(config->pppoe_hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + if (ioctl(pppoediscfd, SIOCGIFMTU, &ifr)) + { + LOG(0, 0, 0, "Error pppoe: ioctl(SIOCGIFMTU): %s\n", strerror(errno)); + exit(1); + } + + if (ifr.ifr_mtu < ETH_DATA_LEN) + LOG(0, 0, 0, "Error pppoe: interface %s has MTU of %i, should be %i\n", config->pppoe_if_name, ifr.ifr_mtu, ETH_DATA_LEN); + + if (ioctl(pppoediscfd, SIOCGIFINDEX, &ifr)) + { + LOG(0, 0, 0, "Error pppoe: ioctl(SIOCGIFINDEX): %s\n", strerror(errno)); + exit(1); + } + + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(ETH_P_PPP_DISC); + sa.sll_ifindex = ifr.ifr_ifindex; + + if (bind(pppoediscfd, (struct sockaddr *)&sa, sizeof(sa))) + { + LOG(0, 0, 0, "Error pppoe: bind: %s\n", strerror(errno)); + exit(1); + } + + if (fcntl(pppoediscfd, F_SETFL, O_NONBLOCK)) + { + LOG(0, 0, 0, "Error pppoe: failed to set nonblocking mode: %s\n", strerror(errno)); + exit(1); + } + +} + +// set up pppoe session socket +static void init_pppoe_sess(void) +{ + int on = 1; + struct ifreq ifr; + struct sockaddr_ll sa; + + memset(&ifr, 0, sizeof(ifr)); + memset(&sa, 0, sizeof(sa)); + + pppoesessfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_PPP_SES)); + if (pppoesessfd < 0) + { + LOG(0, 0, 0, "Error pppoe: socket: %s\n", strerror(errno)); + exit(1); + } + + fcntl(pppoesessfd, F_SETFD, fcntl(pppoesessfd, F_GETFD) | FD_CLOEXEC); + + if (setsockopt(pppoesessfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))) + { + LOG(0, 0, 0, "Error pppoe: setsockopt(SO_BROADCAST): %s\n", strerror(errno)); + exit(1); + } + + assert(strlen(ifr.ifr_name) < sizeof(config->pppoe_if_name) - 1); + if (*config->pppoe_if_name) + strncpy(ifr.ifr_name, config->pppoe_if_name, IFNAMSIZ); + + if (ioctl(pppoesessfd, SIOCGIFHWADDR, &ifr)) + { + LOG(0, 0, 0, "Error pppoe: ioctl(SIOCGIFHWADDR): %s\n", strerror(errno)); + exit(1); + } + + if ((ifr.ifr_hwaddr.sa_data[0] & 1) != 0) + { + LOG(0, 0, 0, "Error pppoe: interface %s has not unicast address\n", config->pppoe_if_name); + exit(1); + } + + memcpy(config->pppoe_hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + if (ioctl(pppoesessfd, SIOCGIFMTU, &ifr)) + { + LOG(0, 0, 0, "Error pppoe: ioctl(SIOCGIFMTU): %s\n", strerror(errno)); + exit(1); + } + + if (ifr.ifr_mtu < ETH_DATA_LEN) + LOG(0, 0, 0, "Error pppoe: interface %s has MTU of %i, should be %i\n", config->pppoe_if_name, ifr.ifr_mtu, ETH_DATA_LEN); + + if (ioctl(pppoesessfd, SIOCGIFINDEX, &ifr)) + { + LOG(0, 0, 0, "Error pppoe: ioctl(SIOCGIFINDEX): %s\n", strerror(errno)); + exit(1); + } + + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(ETH_P_PPP_SES); + sa.sll_ifindex = ifr.ifr_ifindex; + + if (bind(pppoesessfd, (struct sockaddr *)&sa, sizeof(sa))) + { + LOG(0, 0, 0, "Error pppoe: bind: %s\n", strerror(errno)); + exit(1); + } + + if (fcntl(pppoesessfd, F_SETFL, O_NONBLOCK)) + { + LOG(0, 0, 0, "Error pppoe: failed to set nonblocking mode: %s\n", strerror(errno)); + exit(1); + } +} + +// set up pppoe discovery/session socket +void init_pppoe(void) +{ + tunnelidt t = TUNNEL_ID_PPPOE; + + init_pppoe_disc(); + init_pppoe_sess(); + + // Reserve the a pseudo tunnel for pppoe server + if (t > config->cluster_highest_tunnelid) + config->cluster_highest_tunnelid = t; + + memset(&tunnel[t], 0, sizeof(tunnel[t])); + tunnel[t].state = TUNNELOPEN; + STAT(tunnel_created); +} + +char * get_string_codepad(uint8_t codepad) +{ + char * ptrch = NULL; + switch(codepad) + { + case CODE_PADI: + ptrch = code_pad[INDEX_PADI]; + break; + + case CODE_PADO: + ptrch = code_pad[INDEX_PADO]; + break; + + case CODE_PADR: + ptrch = code_pad[INDEX_PADR]; + break; + + case CODE_PADS: + ptrch = code_pad[INDEX_PADS]; + break; + + case CODE_PADT: + ptrch = code_pad[INDEX_PADT]; + break; + + case CODE_SESS: + ptrch = code_pad[INDEX_SESS]; + break; + } + + return ptrch; +} + +static uint8_t * setup_header(uint8_t *pack, const uint8_t *src, const uint8_t *dst, int code, uint16_t sid, uint16_t h_proto) +{ + uint8_t * p; + + // 14 bytes ethernet Header + 6 bytes header pppoe + struct ethhdr *ethhdr = (struct ethhdr *)pack; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + + memcpy(ethhdr->h_source, src, ETH_ALEN); + memcpy(ethhdr->h_dest, dst, ETH_ALEN); + ethhdr->h_proto = htons(h_proto); + + hdr->ver = 1; + hdr->type = 1; + hdr->code = code; + hdr->sid = htons(sid); + hdr->length = 0; + + p = (uint8_t *)(pack + ETH_HLEN + sizeof(*hdr)); + + return p; +} + +static void add_tag(uint8_t *pack, int type, const uint8_t *data, int len) +{ + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + struct pppoe_tag *tag = (struct pppoe_tag *)(pack + ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length)); + + tag->tag_type = htons(type); + tag->tag_len = htons(len); + memcpy(tag->tag_data, data, len); + + hdr->length = htons(ntohs(hdr->length) + sizeof(*tag) + len); +} + +static void add_tag2(uint8_t *pack, const struct pppoe_tag *t) +{ + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + struct pppoe_tag *tag = (struct pppoe_tag *)(pack + ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length)); + + memcpy(tag, t, sizeof(*t) + ntohs(t->tag_len)); + + hdr->length = htons(ntohs(hdr->length) + sizeof(*tag) + ntohs(t->tag_len)); +} + +static void pppoe_disc_send(const uint8_t *pack) +{ + struct ethhdr *ethhdr = (struct ethhdr *)pack; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + int n, s; + + s = ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length); + + LOG(3, 0, 0, "SENT pppoe_disc: Code %s to %s\n", get_string_codepad(hdr->code), fmtMacAddr(ethhdr->h_dest)); + LOG_HEX(5, "pppoe_disc_send", pack, s); + + n = write(pppoediscfd, pack, s); + if (n < 0 ) + LOG(0, 0, 0, "pppoe: write: %s\n", strerror(errno)); + else if (n != s) { + LOG(0, 0, 0, "pppoe: short write %i/%i\n", n,s); + } +} + +void pppoe_sess_send(const uint8_t *pack, uint16_t l, tunnelidt t) +{ + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + int n; + uint16_t sizeppp; + sessionidt s; + + if (t != TUNNEL_ID_PPPOE) + { + LOG(3, 0, t, "ERROR pppoe_sess_send: Tunnel %d is not a tunnel pppoe\n", t); + return; + } + + s = ntohs(hdr->sid); + if (session[s].tunnel != t) + { + LOG(3, s, t, "ERROR pppoe_sess_send: Session is not a session pppoe\n"); + return; + } + + if (l < (ETH_HLEN + sizeof(*hdr) + 3)) + { + LOG(0, s, t, "ERROR pppoe_sess_send: packet too small for pppoe sent (size=%d)\n", l); + return; + } + + // recalculate the ppp frame length + sizeppp = l - (ETH_HLEN + sizeof(*hdr)); + hdr->length = htons(sizeppp); + + LOG_HEX(5, "pppoe_sess_send", pack, l); + + n = write(pppoesessfd, pack, l); + if (n < 0 ) + LOG(0, s, t, "pppoe_sess_send: write: %s\n", strerror(errno)); + else if (n != l) + LOG(0, s, t, "pppoe_sess_send: short write %i/%i\n", n,l); +} + +static void pppoe_send_err(const uint8_t *addr, const struct pppoe_tag *host_uniq, const struct pppoe_tag *relay_sid, int code, int tag_type) +{ + uint8_t pack[ETHER_MAX_LEN]; + + setup_header(pack, config->pppoe_hwaddr, addr, code, 0, ETH_P_PPP_DISC); + + add_tag(pack, TAG_AC_NAME, (uint8_t *)config->pppoe_ac_name, strlen(config->pppoe_ac_name)); + add_tag(pack, tag_type, NULL, 0); + + if (host_uniq) + add_tag2(pack, host_uniq); + + if (relay_sid) + add_tag2(pack, relay_sid); + + pppoe_disc_send(pack); +} + +// generate cookie +static void pppoe_gen_cookie(const uint8_t *serv_hwaddr, const uint8_t *client_hwaddr, uint8_t *out) +{ + MD5_CTX ctx; + + MD5_Init(&ctx); + MD5_Update(&ctx, config->l2tp_secret, strlen(config->l2tp_secret)); + MD5_Update(&ctx, (void *) serv_hwaddr, ETH_ALEN); + MD5_Update(&ctx, (void *) client_hwaddr, ETH_ALEN); + MD5_Final(out, &ctx); +} + +// check cookie +static int pppoe_check_cookie(const uint8_t *serv_hwaddr, const uint8_t *client_hwaddr, uint8_t *cookie) +{ + hasht hash; + + pppoe_gen_cookie(serv_hwaddr, client_hwaddr, hash); + + return memcmp(hash, cookie, 16); +} + +static void pppoe_send_PADO(const uint8_t *addr, const struct pppoe_tag *host_uniq, const struct pppoe_tag *relay_sid, const struct pppoe_tag *service_name) +{ + uint8_t pack[ETHER_MAX_LEN]; + hasht hash; + + setup_header(pack, config->pppoe_hwaddr, addr, CODE_PADO, 0, ETH_P_PPP_DISC); + + add_tag(pack, TAG_AC_NAME, (uint8_t *)config->pppoe_ac_name, strlen(config->pppoe_ac_name)); + + if (service_name) + add_tag2(pack, service_name); + + pppoe_gen_cookie(config->pppoe_hwaddr, addr, hash); + add_tag(pack, TAG_AC_COOKIE, hash, 16); + + if (host_uniq) + add_tag2(pack, host_uniq); + + if (relay_sid) + add_tag2(pack, relay_sid); + + pppoe_disc_send(pack); +} + +static void pppoe_send_PADS(uint16_t sid, const uint8_t *addr, const struct pppoe_tag *host_uniq, const struct pppoe_tag *relay_sid, const struct pppoe_tag *service_name) +{ + uint8_t pack[ETHER_MAX_LEN]; + + setup_header(pack, config->pppoe_hwaddr, addr, CODE_PADS, sid, ETH_P_PPP_DISC); + + add_tag(pack, TAG_AC_NAME, (uint8_t *)config->pppoe_ac_name, strlen(config->pppoe_ac_name)); + + add_tag2(pack, service_name); + + if (host_uniq) + add_tag2(pack, host_uniq); + + if (relay_sid) + add_tag2(pack, relay_sid); + + pppoe_disc_send(pack); +} + +static void pppoe_send_PADT(uint16_t sid) +{ + uint8_t pack[ETHER_MAX_LEN]; + + setup_header(pack, config->pppoe_hwaddr, session[sid].src_hwaddr, CODE_PADT, sid, ETH_P_PPP_DISC); + + add_tag(pack, TAG_AC_NAME, (uint8_t *)config->pppoe_ac_name, strlen(config->pppoe_ac_name)); + + LOG(3, sid, session[sid].tunnel, "pppoe: Sent PADT\n"); + + pppoe_disc_send(pack); +} + +void pppoe_shutdown_session(sessionidt s) +{ + + if (session[s].tunnel != TUNNEL_ID_PPPOE) + { + LOG(3, s, session[s].tunnel, "ERROR pppoe_shutdown_session: Session is not a session pppoe\n"); + return; + } + + pppoe_send_PADT(s); +} + +static void pppoe_recv_PADI(uint8_t *pack, int size) +{ + struct ethhdr *ethhdr = (struct ethhdr *)pack; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + struct pppoe_tag *tag; + struct pppoe_tag *host_uniq_tag = NULL; + struct pppoe_tag *relay_sid_tag = NULL; + struct pppoe_tag *service_name_tag = NULL; + int n, service_match = 0; + int len; + + if (hdr->sid) + return; + + len = ntohs(hdr->length); + for (n = 0; n < len; n += sizeof(*tag) + ntohs(tag->tag_len)) { + tag = (struct pppoe_tag *)(pack + ETH_HLEN + sizeof(*hdr) + n); + if (n + sizeof(*tag) + ntohs(tag->tag_len) > len) + return; + switch (ntohs(tag->tag_type)) { + case TAG_END_OF_LIST: + break; + case TAG_SERVICE_NAME: + if (*config->pppoe_service_name && tag->tag_len) + { + if (ntohs(tag->tag_len) != strlen(config->pppoe_service_name)) + break; + if (memcmp(tag->tag_data, config->pppoe_service_name, ntohs(tag->tag_len))) + break; + service_match = 1; + } + else + { + service_name_tag = tag; + service_match = 1; + } + break; + case TAG_HOST_UNIQ: + host_uniq_tag = tag; + break; + case TAG_RELAY_SESSION_ID: + relay_sid_tag = tag; + break; + } + } + + if (!service_match) + { + LOG(3, 0, 0, "pppoe: discarding PADI packet (Service-Name mismatch)\n"); + return; + } + + pppoe_send_PADO(ethhdr->h_source, host_uniq_tag, relay_sid_tag, service_name_tag); +} + +static void pppoe_recv_PADR(uint8_t *pack, int size) +{ + struct ethhdr *ethhdr = (struct ethhdr *)pack; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + struct pppoe_tag *tag; + struct pppoe_tag *host_uniq_tag = NULL; + struct pppoe_tag *relay_sid_tag = NULL; + struct pppoe_tag *ac_cookie_tag = NULL; + struct pppoe_tag *service_name_tag = NULL; + int n, service_match = 0; + uint16_t sid; + + if (!memcmp(ethhdr->h_dest, bc_addr, ETH_ALEN)) + { + LOG(1, 0, 0, "Rcv pppoe: discard PADR (destination address is broadcast)\n"); + return; + } + + if (hdr->sid) + { + LOG(1, 0, 0, "Rcv pppoe: discarding PADR packet (sid is not zero)\n"); + return; + } + + for (n = 0; n < ntohs(hdr->length); n += sizeof(*tag) + ntohs(tag->tag_len)) + { + tag = (struct pppoe_tag *)(pack + ETH_HLEN + sizeof(*hdr) + n); + switch (ntohs(tag->tag_type)) + { + case TAG_END_OF_LIST: + break; + case TAG_SERVICE_NAME: + service_name_tag = tag; + if (tag->tag_len == 0) + service_match = 1; + else if (*config->pppoe_service_name) + { + if (ntohs(tag->tag_len) != strlen(config->pppoe_service_name)) + break; + if (memcmp(tag->tag_data, config->pppoe_service_name, ntohs(tag->tag_len))) + break; + service_match = 1; + } + else + { + service_match = 1; + } + break; + case TAG_HOST_UNIQ: + host_uniq_tag = tag; + break; + case TAG_AC_COOKIE: + ac_cookie_tag = tag; + break; + case TAG_RELAY_SESSION_ID: + relay_sid_tag = tag; + break; + } + } + + if (!service_match) + { + LOG(3, 0, 0, "pppoe: Service-Name mismatch\n"); + pppoe_send_err(ethhdr->h_source, host_uniq_tag, relay_sid_tag, CODE_PADS, TAG_SERVICE_NAME_ERROR); + return; + } + + if (!ac_cookie_tag) + { + LOG(3, 0, 0, "pppoe: discard PADR packet (no AC-Cookie tag present)\n"); + return; + } + + if (ntohs(ac_cookie_tag->tag_len) != 16) + { + LOG(3, 0, 0, "pppoe: discard PADR packet (incorrect AC-Cookie tag length)\n"); + return; + } + + if (pppoe_check_cookie(ethhdr->h_dest, ethhdr->h_source, (uint8_t *) ac_cookie_tag->tag_data)) + { + LOG(3, 0, 0, "pppoe: discard PADR packet (incorrect AC-Cookie)\n"); + return; + } + + sid = sessionfree; + sessionfree = session[sid].next; + memset(&session[sid], 0, sizeof(session[0])); + + if (sid > config->cluster_highest_sessionid) + config->cluster_highest_sessionid = sid; + + session[sid].opened = time_now; + session[sid].tunnel = TUNNEL_ID_PPPOE; + session[sid].last_packet = session[sid].last_data = time_now; + + //strncpy(session[sid].called, called, sizeof(session[sid].called) - 1); + //strncpy(session[sid].calling, calling, sizeof(session[sid].calling) - 1); + + session[sid].ppp.phase = Establish; + session[sid].ppp.lcp = Starting; + + session[sid].magic = time_now; // set magic number + session[sid].mru = PPPoE_MRU; // default + + // start LCP + sess_local[sid].lcp_authtype = config->radius_authprefer; + sess_local[sid].ppp_mru = MRU; + + // Set multilink options before sending initial LCP packet + sess_local[sid].mp_mrru = 1614; + sess_local[sid].mp_epdis = ntohl(config->iftun_address ? config->iftun_address : my_address); + + memcpy(session[sid].src_hwaddr, ethhdr->h_source, ETH_ALEN); + pppoe_send_PADS(sid, ethhdr->h_source, host_uniq_tag, relay_sid_tag, service_name_tag); + + sendlcp(sid, session[sid].tunnel); + change_state(sid, lcp, RequestSent); + +} + +static void pppoe_recv_PADT(uint8_t *pack) +{ + struct ethhdr *ethhdr = (struct ethhdr *)pack; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + + if (!memcmp(ethhdr->h_dest, bc_addr, ETH_ALEN)) + { + LOG(3, 0, 0, "pppoe: discard PADT (destination address is broadcast)\n"); + return; + } + + if (hdr->sid) + { + if ((hdr->sid < MAXSESSION) && (session[hdr->sid].tunnel == TUNNEL_ID_PPPOE)) + sessionshutdown(hdr->sid, "Client shutdown", CDN_ADMIN_DISC, 0); + } +} + +// fill in a PPPOE message with a PPP frame, +// returns start of PPP frame +uint8_t *pppoe_makeppp(uint8_t *b, int size, uint8_t *p, int l, sessionidt s, tunnelidt t, + uint16_t mtype, uint8_t prio, bundleidt bid, uint8_t mp_bits) +{ + uint16_t type = mtype; + uint8_t *start = b; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(b + ETH_HLEN); + + if (t != TUNNEL_ID_PPPOE) + return NULL; + + if (size < 28) // Need more space than this!! + { + LOG(0, s, t, "pppoe_makeppp buffer too small for pppoe header (size=%d)\n", size); + return NULL; + } + + // 14 bytes ethernet Header + 6 bytes header pppoe + b = setup_header(b, config->pppoe_hwaddr, session[s].src_hwaddr, CODE_SESS, s, ETH_P_PPP_SES); + + // Check whether this session is part of multilink + if (bid) + { + if (bundle[bid].num_of_links > 1) + type = PPPMP; // Change PPP message type to the PPPMP + else + bid = 0; + } + + *(uint16_t *) b = htons(type); + b += 2; + hdr->length += 2; + + if (bid) + { + // Set the sequence number and (B)egin (E)nd flags + if (session[s].mssf) + { + // Set the multilink bits + uint16_t bits_send = mp_bits; + *(uint16_t *) b = htons((bundle[bid].seq_num_t & 0x0FFF)|bits_send); + b += 2; + hdr->length += 2; + } + else + { + *(uint32_t *) b = htonl(bundle[bid].seq_num_t); + // Set the multilink bits + *b = mp_bits; + b += 4; + hdr->length += 4; + } + + bundle[bid].seq_num_t++; + + // Add the message type if this fragment has the begin bit set + if (mp_bits & MP_BEGIN) + { + //*b++ = mtype; // The next two lines are instead of this + *(uint16_t *) b = htons(mtype); // Message type + b += 2; + hdr->length += 2; + } + } + + if ((b - start) + l > size) + { + LOG(0, s, t, "pppoe_makeppp would overflow buffer (size=%d, header+payload=%td)\n", size, (b - start) + l); + return NULL; + } + + // Copy the payload + if (p && l) + { + memcpy(b, p, l); + hdr->length += l; + } + + return b; +} + +// pppoe discovery recv data +void process_pppoe_disc(uint8_t *pack, int size) +{ + struct ethhdr *ethhdr = (struct ethhdr *)pack; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + + LOG(3, 0, 0, "RCV pppoe_disc: Code %s from %s\n", get_string_codepad(hdr->code), fmtMacAddr(ethhdr->h_source)); + LOG_HEX(5, "PPPOE Disc", pack, size); + + if (!config->cluster_iam_master) + { + if (hdr->code == CODE_PADI) + return; // Discard because the PADI is received by all (PADI is a broadcast diffusion) + + master_forward_pppoe_packet(pack, size, hdr->code); + return; + } + + if (size < (ETH_HLEN + sizeof(*hdr))) + { + LOG(1, 0, 0, "Error pppoe_disc: short packet received (%i)\n", size); + return; + } + + if (memcmp(ethhdr->h_dest, bc_addr, ETH_ALEN) && memcmp(ethhdr->h_dest, config->pppoe_hwaddr, ETH_ALEN)) + { + LOG(1, 0, 0, "Error pppoe_disc: h_dest != bc_addr and h_dest != config->pppoe_hwaddr\n"); + return; + } + + if (!memcmp(ethhdr->h_source, bc_addr, ETH_ALEN)) + { + LOG(1, 0, 0, "Error pppoe_disc: discarding packet (source address is broadcast)\n"); + return; + } + + if ((ethhdr->h_source[0] & 1) != 0) + { + LOG(1, 0, 0, "Error pppoe_disc: discarding packet (host address is not unicast)\n"); + return; + } + + if (size < ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length)) + { + LOG(1, 0, 0, "Error pppoe_disc: short packet received\n"); + return; + } + + if (hdr->ver != 1) + { + LOG(1, 0, 0, "Error pppoe_disc: discarding packet (unsupported version %i)\n", hdr->ver); + return; + } + + if (hdr->type != 1) + { + LOG(1, 0, 0, "Error pppoe_disc: discarding packet (unsupported type %i)\n", hdr->type); + return; + } + + switch (hdr->code) { + case CODE_PADI: + pppoe_recv_PADI(pack, size); + break; + case CODE_PADR: + pppoe_recv_PADR(pack, size); + break; + case CODE_PADT: + pppoe_recv_PADT(pack); + break; + } +} + +#ifdef LAC +// Forward from pppoe to l2tp remote LNS +static void pppoe_forwardto_session_rmlns(uint8_t *pack, int size, sessionidt sess, uint16_t proto) +{ + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + uint16_t lppp = ntohs(hdr->length); + uint16_t ll2tp = lppp + 6; + uint8_t *pppdata = (uint8_t *) hdr->tag; + uint8_t *pl2tp = pppdata - 6; + uint8_t *p = pl2tp; + uint16_t t = 0, s = 0; + + s = session[sess].forwardtosession; + if (session[s].forwardtosession != sess) + { + LOG(3, sess, session[sess].tunnel, "pppoe: Link Session (%u) broken\n", s); + return; + } + + t = session[s].tunnel; + if (t >= MAXTUNNEL) + { + LOG(1, s, t, "pppoe: Session with invalid tunnel ID\n"); + return; + } + + if (!tunnel[t].isremotelns) + { + LOG(3, sess, session[sess].tunnel, "pppoe: Link Tunnel/Session (%u/%u) broken\n", s, t); + return; + } + + // First word L2TP options (with no options) + *(uint16_t *) p = htons(0x0002); + p += 2; + *(uint16_t *) p = htons(tunnel[t].far); // tunnel + p += 2; + *(uint16_t *) p = htons(session[s].far); // session + p += 2; + + if ((proto == PPPIP) || (proto == PPPMP) ||(proto == PPPIPV6 && config->ipv6_prefix.s6_addr[0])) + { + session[sess].last_packet = session[sess].last_data = time_now; + // Update STAT IN + increment_counter(&session[sess].cin, &session[sess].cin_wrap, ll2tp); + session[sess].cin_delta += ll2tp; + session[sess].pin++; + sess_local[sess].cin += ll2tp; + sess_local[sess].pin++; + + session[s].last_data = time_now; + // Update STAT OUT + increment_counter(&session[s].cout, &session[s].cout_wrap, ll2tp); // byte count + session[s].cout_delta += ll2tp; + session[s].pout++; + sess_local[s].cout += ll2tp; + sess_local[s].pout++; + } + else + session[sess].last_packet = time_now; + + tunnelsend(pl2tp, ll2tp, t); // send it... +} + +// Forward from l2tp to pppoe +// (note: THIS ROUTINE WRITES TO pack[-20]). +void pppoe_forwardto_session_pppoe(uint8_t *pack, int size, sessionidt sess, uint16_t proto) +{ + uint16_t t = 0, s = 0; + uint16_t lpppoe = size - 2; + uint8_t *p = pack + 2; // First word L2TP options + + LOG(5, sess, session[sess].tunnel, "Forwarding data session to pppoe session %u\n", session[sess].forwardtosession); + + s = session[sess].forwardtosession; + t = session[s].tunnel; + + if (*pack & 0x40) + { // length + p += 2; + lpppoe -= 2; + } + + *(uint16_t *) p = htons(tunnel[t].far); // tunnel + p += 2; + *(uint16_t *) p = htons(session[s].far); // session + p += 2; + lpppoe -= 4; + + if (*pack & 0x08) + { // ns/nr + *(uint16_t *) p = htons(tunnel[t].ns); // sequence + p += 2; + *(uint16_t *) p = htons(tunnel[t].nr); // sequence + p += 2; + lpppoe -= 4; + } + + if (lpppoe > 2 && p[0] == 0xFF && p[1] == 0x03) + { + // HDLC address header, discard in pppoe + p += 2; + lpppoe -= 2; + } + + lpppoe += (ETH_HLEN + sizeof(struct pppoe_hdr)); + p -= (ETH_HLEN + sizeof(struct pppoe_hdr)); + + // 14 bytes ethernet Header + 6 bytes header pppoe + setup_header(p, config->pppoe_hwaddr, session[s].src_hwaddr, CODE_SESS, s, ETH_P_PPP_SES); + + if ((proto == PPPIP) || (proto == PPPMP) ||(proto == PPPIPV6 && config->ipv6_prefix.s6_addr[0])) + { + session[sess].last_packet = session[sess].last_data = time_now; + // Update STAT IN + increment_counter(&session[sess].cin, &session[sess].cin_wrap, lpppoe); + session[sess].cin_delta += lpppoe; + session[sess].pin++; + sess_local[sess].cin += lpppoe; + sess_local[sess].pin++; + + session[s].last_data = time_now; + // Update STAT OUT + increment_counter(&session[s].cout, &session[s].cout_wrap, lpppoe); // byte count + session[s].cout_delta += lpppoe; + session[s].pout++; + sess_local[s].cout += lpppoe; + sess_local[s].pout++; + } + else + session[sess].last_packet = time_now; + + tunnelsend(p, lpppoe, t); // send it.... +} +#endif + +void process_pppoe_sess(uint8_t *pack, int size) +{ + //struct ethhdr *ethhdr = (struct ethhdr *)pack; + struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); + uint16_t lppp = ntohs(hdr->length); + uint8_t *pppdata = (uint8_t *) hdr->tag; + uint16_t proto, sid, t; + + sid = ntohs(hdr->sid); + t = TUNNEL_ID_PPPOE; + + LOG_HEX(5, "RCV PPPOE Sess", pack, size); + + if (sid >= MAXSESSION) + { + LOG(0, sid, t, "Received pppoe packet with invalid session ID\n"); + STAT(tunnel_rx_errors); + return; + } + + if (session[sid].tunnel != t) + { + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + + LOG(1, sid, t, "ERROR process_pppoe_sess: Session is not a session pppoe\n"); + return; + } + + if (hdr->ver != 1) + { + LOG(3, sid, t, "Error process_pppoe_sess: discarding packet (unsupported version %i)\n", hdr->ver); + return; + } + + if (hdr->type != 1) + { + LOG(3, sid, t, "Error process_pppoe_sess: discarding packet (unsupported type %i)\n", hdr->type); + return; + } + + if (lppp > 2 && pppdata[0] == 0xFF && pppdata[1] == 0x03) + { // HDLC address header, discard + LOG(5, sid, t, "pppoe_sess: HDLC address header, discard\n"); + pppdata += 2; + lppp -= 2; + } + if (lppp < 2) + { + LOG(3, sid, t, "Error process_pppoe_sess: Short ppp length %d\n", lppp); + return; + } + if (*pppdata & 1) + { + proto = *pppdata++; + lppp--; + } + else + { + proto = ntohs(*(uint16_t *) pppdata); + pppdata += 2; + lppp -= 2; + } + +#ifdef LAC + if (session[sid].forwardtosession) + { // Must be forwaded to a remote lns tunnel l2tp + pppoe_forwardto_session_rmlns(pack, size, sid, proto); + return; + } +#endif + + if (proto == PPPPAP) + { + session[sid].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processpap(sid, t, pppdata, lppp); + } + else if (proto == PPPCHAP) + { + session[sid].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processchap(sid, t, pppdata, lppp); + } + else if (proto == PPPLCP) + { + session[sid].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processlcp(sid, t, pppdata, lppp); + } + else if (proto == PPPIPCP) + { + session[sid].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processipcp(sid, t, pppdata, lppp); + } + else if (proto == PPPIPV6CP && config->ipv6_prefix.s6_addr[0]) + { + session[sid].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processipv6cp(sid, t, pppdata, lppp); + } + else if (proto == PPPCCP) + { + session[sid].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processccp(sid, t, pppdata, lppp); + } + else if (proto == PPPIP) + { + session[sid].last_packet = session[sid].last_data = time_now; + if (session[sid].walled_garden && !config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processipin(sid, t, pppdata, lppp); + } + else if (proto == PPPMP) + { + session[sid].last_packet = session[sid].last_data = time_now; + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processmpin(sid, t, pppdata, lppp); + } + else if (proto == PPPIPV6 && config->ipv6_prefix.s6_addr[0]) + { + session[sid].last_packet = session[sid].last_data = time_now; + if (session[sid].walled_garden && !config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + processipv6in(sid, t, pppdata, lppp); + } + else if (session[sid].ppp.lcp == Opened) + { + session[sid].last_packet = time_now; + if (!config->cluster_iam_master) { master_forward_pppoe_packet(pack, size, hdr->code); return; } + protoreject(sid, t, pppdata, lppp, proto); + } + else + { + LOG(3, sid, t, "process_pppoe_sess: Unknown PPP protocol 0x%04X received in LCP %s state\n", + proto, ppp_state(session[sid].ppp.lcp)); + } +} + +void pppoe_send_garp() +{ + int s; + struct ifreq ifr; + uint8_t mac[6]; + + if (!*config->pppoe_if_name) + return; + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + LOG(0, 0, 0, "Error creating socket for GARP: %s\n", strerror(errno)); + return; + } + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, config->pppoe_if_name, sizeof(ifr.ifr_name) - 1); + if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) + { + LOG(0, 0, 0, "Error getting eth0 hardware address for GARP: %s\n", strerror(errno)); + close(s); + return; + } + memcpy(mac, &ifr.ifr_hwaddr.sa_data, 6*sizeof(char)); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) + { + LOG(0, 0, 0, "Error getting eth0 interface index for GARP: %s\n", strerror(errno)); + close(s); + return; + } + close(s); + + sendarp(ifr.ifr_ifindex, mac, config->iftun_address); +} + +// rcv pppoe data from slave +void pppoe_process_forward(uint8_t *pack, int size, in_addr_t addr) +{ + struct ethhdr *ethhdr = (struct ethhdr *)pack; + + if (ethhdr->h_proto == htons(ETH_P_PPP_DISC)) + process_pppoe_disc(pack, size); + else if (ethhdr->h_proto == htons(ETH_P_PPP_SES)) + process_pppoe_sess(pack, size); + else + LOG(0, 0, 0, "pppoe_process_forward: I got a C_PPPOE_FORWARD from %s, but not a PPPOE data?\n", fmtaddr(addr, 0)); +} diff --git a/pppoe.h b/pppoe.h new file mode 100644 index 0000000..62a8208 --- /dev/null +++ b/pppoe.h @@ -0,0 +1,23 @@ + +#ifndef __PPPOE_H__ +#define __PPPOE_H__ + +#define DEFAULT_PPPOE_AC_NAME "l2tpns-pppoe" + +// pppoe.c +void init_pppoe(void); +void process_pppoe_disc(uint8_t *pack, int size); +void process_pppoe_sess(uint8_t *pack, int size); +void pppoe_sess_send(const uint8_t *pack, uint16_t l, tunnelidt t); +uint8_t *pppoe_makeppp(uint8_t *b, int size, uint8_t *p, int l, sessionidt s, tunnelidt t, + uint16_t mtype, uint8_t prio, bundleidt bid, uint8_t mp_bits); +void pppoe_shutdown_session(sessionidt s); +void pppoe_forwardto_session_pppoe(uint8_t *pack, int size, sessionidt sess, uint16_t proto); +void pppoe_process_forward(uint8_t *pack, int size, in_addr_t addr); +void pppoe_send_garp(); +char * get_string_codepad(uint8_t codepad); + +extern int pppoediscfd; // pppoe discovery socket +extern int pppoesessfd; // pppoe session socket + +#endif /* __PPPOE_H__ */ diff --git a/radius.c b/radius.c index e783323..34806c5 100644 --- a/radius.c +++ b/radius.c @@ -22,6 +22,7 @@ #ifdef LAC #include "l2tplac.h" #endif +#include "pppoe.h" extern radiust *radius; extern sessiont *session; diff --git a/util.c b/util.c index e132be6..d4dbd4f 100644 --- a/util.c +++ b/util.c @@ -28,6 +28,17 @@ char *fmtaddr(in_addr_t addr, int n) return strcpy(addrs[n], inet_ntoa(in)); } +char *fmtMacAddr(uint8_t *pMacAddr) +{ + static char strMAC[2*ETH_ALEN]; + + sprintf(strMAC, "%02X:%02X:%02X:%02X:%02X:%02X", + pMacAddr[0], pMacAddr[1], pMacAddr[2], + pMacAddr[3], pMacAddr[4], pMacAddr[5]); + + return strMAC; +} + void *shared_malloc(unsigned int size) { void * p; diff --git a/util.h b/util.h index ee066f6..2345860 100644 --- a/util.h +++ b/util.h @@ -2,6 +2,7 @@ #define __UTIL_H__ char *fmtaddr(in_addr_t addr, int n); +char *fmtMacAddr(uint8_t *pMacAddr); void *shared_malloc(unsigned int size); pid_t fork_and_close(void); ssize_t sendtofrom(int s, void const *buf, size_t len, int flags, -- 2.20.1