summaryrefslogtreecommitdiff
path: root/accel-pppd/ctrl/pppoe
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd/ctrl/pppoe')
-rw-r--r--accel-pppd/ctrl/pppoe/CMakeLists.txt17
-rw-r--r--accel-pppd/ctrl/pppoe/cli.c205
-rw-r--r--accel-pppd/ctrl/pppoe/dpado.c162
-rw-r--r--accel-pppd/ctrl/pppoe/mac_filter.c255
-rw-r--r--accel-pppd/ctrl/pppoe/pppoe.c1288
-rw-r--r--accel-pppd/ctrl/pppoe/pppoe.h117
-rw-r--r--accel-pppd/ctrl/pppoe/tr101.c97
7 files changed, 2141 insertions, 0 deletions
diff --git a/accel-pppd/ctrl/pppoe/CMakeLists.txt b/accel-pppd/ctrl/pppoe/CMakeLists.txt
new file mode 100644
index 0000000..b2c0584
--- /dev/null
+++ b/accel-pppd/ctrl/pppoe/CMakeLists.txt
@@ -0,0 +1,17 @@
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
+
+SET(sources
+ pppoe.c
+ mac_filter.c
+ dpado.c
+ cli.c
+)
+
+IF (RADIUS)
+SET(sources ${sources} tr101.c)
+ENDIF(RADIUS)
+
+ADD_LIBRARY(pppoe SHARED ${sources})
+TARGET_LINK_LIBRARIES(pppoe crypto)
+
+INSTALL(TARGETS pppoe LIBRARY DESTINATION lib/accel-ppp)
diff --git a/accel-pppd/ctrl/pppoe/cli.c b/accel-pppd/ctrl/pppoe/cli.c
new file mode 100644
index 0000000..9929f66
--- /dev/null
+++ b/accel-pppd/ctrl/pppoe/cli.c
@@ -0,0 +1,205 @@
+#include <string.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <net/ethernet.h>
+
+#include "triton.h"
+#include "cli.h"
+#include "ppp.h"
+#include "memdebug.h"
+
+#include "pppoe.h"
+
+static void show_interfaces(void *cli)
+{
+ struct pppoe_serv_t *serv;
+
+ cli_send(cli, "interface: connections: state:\r\n");
+ cli_send(cli, "-----------------------------------\r\n");
+
+ pthread_rwlock_rdlock(&serv_lock);
+ list_for_each_entry(serv, &serv_list, entry) {
+ cli_sendv(cli, "%9s %11u %6s\r\n", serv->ifname, serv->conn_cnt, serv->stopping ? "stop" : "active");
+ }
+ pthread_rwlock_unlock(&serv_lock);
+}
+
+static void intf_help(char * const *fields, int fields_cnt, void *client)
+{
+ cli_send(client, "pppoe interface add <name> - start pppoe server on specified interface\r\n");
+ cli_send(client, "pppoe interface del <name> - stop pppoe server on specified interface and drop his connections\r\n");
+ cli_send(client, "pppoe interface show - show interfaces on which pppoe server started\r\n");
+}
+
+static int intf_exec(const char *cmd, char * const *fields, int fields_cnt, void *client)
+{
+ if (fields_cnt == 2)
+ goto help;
+
+ if (fields_cnt == 3) {
+ if (!strcmp(fields[2], "show"))
+ show_interfaces(client);
+ else
+ goto help;
+
+ return CLI_CMD_OK;
+ }
+
+ if (fields_cnt != 4)
+ goto help;
+
+ if (!strcmp(fields[2], "add"))
+ pppoe_server_start(fields[3], client);
+ else if (!strcmp(fields[2], "del"))
+ pppoe_server_stop(fields[3]);
+ else
+ goto help;
+
+ return CLI_CMD_OK;
+help:
+ intf_help(fields, fields_cnt, client);
+ return CLI_CMD_OK;
+}
+
+//===================================
+
+static int show_stat_exec(const char *cmd, char * const *fields, int fields_cnt, void *client)
+{
+ cli_send(client, "pppoe:\r\n");
+ cli_sendv(client, " active: %u\r\n", stat_active);
+ cli_sendv(client, " delayed PADO: %u\r\n", stat_delayed_pado);
+ cli_sendv(client, " recv PADI: %lu\r\n", stat_PADI_recv);
+ cli_sendv(client, " sent PADO: %lu\r\n", stat_PADO_sent);
+ cli_sendv(client, " recv PADR(dup): %lu(%lu)\r\n", stat_PADR_recv, stat_PADR_dup_recv);
+ cli_sendv(client, " sent PADS: %lu\r\n", stat_PADS_sent);
+
+ return CLI_CMD_OK;
+}
+
+//===================================
+
+static void set_verbose_help(char * const *f, int f_cnt, void *cli)
+{
+ cli_send(cli, "pppoe set verbose <n> - set verbosity of pppoe logging\r\n");
+ cli_send(cli, "pppoe set PADO-delay <delay[,delay1:count1[,delay2:count2[,...]]]> - set PADO delays (ms)\r\n");
+ cli_send(cli, "pppoe set Service-Name <name> - set Service-Name to respond\r\n");
+ cli_send(cli, "pppoe set Service-Name * - respond with client's Service-Name\r\n");
+ cli_send(cli, "pppoe set AC-Name <name> - set AC-Name tag value\r\n");
+ cli_send(cli, "pppoe show verbose - show current verbose value\r\n");
+ cli_send(cli, "pppoe show PADO-delay - show current PADO delay value\r\n");
+ cli_send(cli, "pppoe show Service-Name - show current Service-Name value\r\n");
+ cli_send(cli, "pppoe show AC-Name - show current AC-Name tag value\r\n");
+}
+
+static int show_verbose_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
+{
+ if (f_cnt != 3)
+ return CLI_CMD_SYNTAX;
+
+ cli_sendv(cli, "%i\r\n", conf_verbose);
+
+ return CLI_CMD_OK;
+}
+
+static int show_pado_delay_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
+{
+ if (f_cnt != 3)
+ return CLI_CMD_SYNTAX;
+
+ cli_sendv(cli, "%s\r\n", conf_pado_delay);
+
+ return CLI_CMD_OK;
+}
+
+static int show_service_name_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
+{
+ if (f_cnt != 3)
+ return CLI_CMD_SYNTAX;
+
+ if (conf_service_name)
+ cli_sendv(cli, "%s\r\n", conf_service_name);
+ else
+ cli_sendv(cli, "*\r\n", conf_service_name);
+
+ return CLI_CMD_OK;
+}
+
+static int show_ac_name_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
+{
+ if (f_cnt != 3)
+ return CLI_CMD_SYNTAX;
+
+ cli_sendv(cli, "%s\r\n", conf_ac_name);
+
+ return CLI_CMD_OK;
+}
+
+static int set_verbose_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
+{
+ if (f_cnt != 4)
+ return CLI_CMD_SYNTAX;
+
+ if (!strcmp(f[3], "0"))
+ conf_verbose = 0;
+ else if (!strcmp(f[3], "1"))
+ conf_verbose = 1;
+ else
+ return CLI_CMD_INVAL;
+
+ return CLI_CMD_OK;
+}
+
+static int set_pado_delay_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
+{
+ if (f_cnt != 4)
+ return CLI_CMD_SYNTAX;
+
+ if (dpado_parse(f[3]))
+ return CLI_CMD_INVAL;
+
+ return CLI_CMD_OK;
+}
+
+static int set_service_name_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
+{
+ if (f_cnt != 4)
+ return CLI_CMD_SYNTAX;
+
+ if (conf_service_name)
+ _free(conf_service_name);
+
+ if (!strcmp(f[3], "*"))
+ conf_service_name = NULL;
+ else
+ conf_service_name = _strdup(f[3]);
+
+ return CLI_CMD_OK;
+}
+
+static int set_ac_name_exec(const char *cmd, char * const *f, int f_cnt, void *cli)
+{
+ if (f_cnt != 4)
+ return CLI_CMD_SYNTAX;
+
+ _free(conf_ac_name);
+ conf_ac_name = _strdup(f[3]);
+
+ return CLI_CMD_OK;
+}
+//===================================
+
+
+static void __init init(void)
+{
+ cli_register_simple_cmd2(show_stat_exec, NULL, 2, "show", "stat");
+ cli_register_simple_cmd2(intf_exec, intf_help, 2, "pppoe", "interface");
+ cli_register_simple_cmd2(set_verbose_exec, set_verbose_help, 3, "pppoe", "set", "verbose");
+ cli_register_simple_cmd2(set_pado_delay_exec, NULL, 3, "pppoe", "set", "PADO-delay");
+ cli_register_simple_cmd2(set_service_name_exec, NULL, 3, "pppoe", "set", "Service-Name");
+ cli_register_simple_cmd2(set_ac_name_exec, NULL, 3, "pppoe", "set", "AC-Name");
+ cli_register_simple_cmd2(show_verbose_exec, NULL, 3, "pppoe", "show", "verbose");
+ cli_register_simple_cmd2(show_pado_delay_exec, NULL, 3, "pppoe", "show", "PADO-delay");
+ cli_register_simple_cmd2(show_service_name_exec, NULL, 3, "pppoe", "show", "Service-Name");
+ cli_register_simple_cmd2(show_ac_name_exec, NULL, 3, "pppoe", "show", "AC-Name");
+}
+
diff --git a/accel-pppd/ctrl/pppoe/dpado.c b/accel-pppd/ctrl/pppoe/dpado.c
new file mode 100644
index 0000000..3f56519
--- /dev/null
+++ b/accel-pppd/ctrl/pppoe/dpado.c
@@ -0,0 +1,162 @@
+#include <stdio.h>
+#include <string.h>
+#include <pthread.h>
+#include <errno.h>
+#include <limits.h>
+#include <netinet/in.h>
+#include <net/ethernet.h>
+
+#include "list.h"
+#include "cli.h"
+#include "triton.h"
+#include "log.h"
+#include "memdebug.h"
+
+#include "pppoe.h"
+
+struct dpado_range_t
+{
+ struct list_head entry;
+ unsigned int conn_cnt;
+ int pado_delay;
+};
+
+static pthread_mutex_t dpado_range_lock = PTHREAD_MUTEX_INITIALIZER;
+static LIST_HEAD(dpado_range_list);
+static struct dpado_range_t *dpado_range_next;
+static struct dpado_range_t *dpado_range_prev;
+int pado_delay;
+
+void dpado_check_next(int conn_cnt)
+{
+ pthread_mutex_lock(&dpado_range_lock);
+ if (dpado_range_next && conn_cnt == dpado_range_next->conn_cnt) {
+ pado_delay = dpado_range_next->pado_delay;
+ dpado_range_prev = dpado_range_next;
+ if (dpado_range_next->entry.next != &dpado_range_list)
+ dpado_range_next = list_entry(dpado_range_next->entry.next, typeof(*dpado_range_next), entry);
+ else
+ dpado_range_next = NULL;
+ /*printf("active=%i, prev=%i:%i, next=%i:%i, pado_delay=%i\n", stat_active,
+ dpado_range_prev?dpado_range_prev->pado_delay:0,dpado_range_prev?dpado_range_prev->conn_cnt:0,
+ dpado_range_next?dpado_range_next->pado_delay:0,dpado_range_next?dpado_range_next->conn_cnt:0,
+ pado_delay);*/
+ }
+ pthread_mutex_unlock(&dpado_range_lock);
+}
+
+void dpado_check_prev(int conn_cnt)
+{
+ pthread_mutex_lock(&dpado_range_lock);
+ if (dpado_range_prev && conn_cnt == dpado_range_prev->conn_cnt) {
+ dpado_range_next = dpado_range_prev;
+ dpado_range_prev = list_entry(dpado_range_prev->entry.prev, typeof(*dpado_range_prev), entry);
+ pado_delay = dpado_range_prev->pado_delay;
+ /*printf("active=%i, prev=%i:%i, next=%i:%i, pado_delay=%i\n", stat_active,
+ dpado_range_prev?dpado_range_prev->pado_delay:0,dpado_range_prev?dpado_range_prev->conn_cnt:0,
+ dpado_range_next?dpado_range_next->pado_delay:0,dpado_range_next?dpado_range_next->conn_cnt:0,
+ pado_delay);*/
+ }
+ pthread_mutex_unlock(&dpado_range_lock);
+}
+
+static void strip(char *str)
+{
+ char *ptr = str;
+ char *endptr = strchr(str, 0);
+ while (1) {
+ ptr = strchr(ptr, ' ');
+ if (ptr)
+ memmove(ptr, ptr + 1, endptr - ptr - 1);
+ else
+ break;
+ }
+}
+
+int dpado_parse(const char *str)
+{
+ char *str1 = _strdup(str);
+ char *ptr1, *ptr2, *ptr3, *endptr;
+ LIST_HEAD(range_list);
+ struct dpado_range_t *r;
+
+ strip(str1);
+
+ ptr1 = str1;
+
+ while (1) {
+ ptr2 = strchr(ptr1, ',');
+ if (ptr2)
+ *ptr2 = 0;
+ ptr3 = strchr(ptr1, ':');
+ if (ptr3)
+ *ptr3 = 0;
+
+ r = _malloc(sizeof(*r));
+ memset(r, 0, sizeof(*r));
+
+ r->pado_delay = strtol(ptr1, &endptr, 10);
+ if (*endptr)
+ goto out_err;
+
+ if (list_empty(&range_list))
+ r->conn_cnt = INT_MAX;
+ else {
+ if (!ptr3)
+ goto out_err;
+ r->conn_cnt = strtol(ptr3 + 1, &endptr, 10);
+ if (*endptr)
+ goto out_err;
+ }
+
+ list_add_tail(&r->entry, &range_list);
+ //printf("parsed range: %i:%i\n", r->pado_delay, r->conn_cnt);
+
+ if (!ptr2)
+ break;
+
+ ptr1 = ptr2 + 1;
+ }
+
+ pthread_mutex_lock(&dpado_range_lock);
+ while (!list_empty(&dpado_range_list)) {
+ r = list_entry(dpado_range_list.next, typeof(*r), entry);
+ list_del(&r->entry);
+ _free(r);
+ }
+
+ dpado_range_next = NULL;
+ dpado_range_prev = NULL;
+
+ while (!list_empty(&range_list)) {
+ r = list_entry(range_list.next, typeof(*r), entry);
+ list_del(&r->entry);
+ list_add_tail(&r->entry, &dpado_range_list);
+
+ if (!dpado_range_prev || stat_active >= r->conn_cnt)
+ dpado_range_prev = r;
+ else if (!dpado_range_next)
+ dpado_range_next = r;
+ }
+
+ pado_delay = dpado_range_prev->pado_delay;
+
+ if (conf_pado_delay)
+ _free(conf_pado_delay);
+ conf_pado_delay = _strdup(str);
+ /*printf("active=%i, prev=%i:%i, next=%i:%i, pado_delay=%i\n", stat_active,
+ dpado_range_prev?dpado_range_prev->pado_delay:0,dpado_range_prev?dpado_range_prev->conn_cnt:0,
+ dpado_range_next?dpado_range_next->pado_delay:0,dpado_range_next?dpado_range_next->conn_cnt:0,
+ pado_delay);*/
+
+ pthread_mutex_unlock(&dpado_range_lock);
+
+ _free(str1);
+ return 0;
+
+out_err:
+ _free(str1);
+ log_emerg("pppoe: pado_delay: invalid format\n");
+ return -1;
+}
+
diff --git a/accel-pppd/ctrl/pppoe/mac_filter.c b/accel-pppd/ctrl/pppoe/mac_filter.c
new file mode 100644
index 0000000..9b101c6
--- /dev/null
+++ b/accel-pppd/ctrl/pppoe/mac_filter.c
@@ -0,0 +1,255 @@
+#include <stdio.h>
+#include <string.h>
+#include <pthread.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <net/ethernet.h>
+
+#include "list.h"
+#include "cli.h"
+#include "triton.h"
+#include "log.h"
+#include "ppp.h"
+#include "memdebug.h"
+
+#include "pppoe.h"
+
+struct mac_t
+{
+ struct list_head entry;
+ uint8_t addr[ETH_ALEN];
+};
+
+static LIST_HEAD(mac_list);
+static int type; // -1 - disabled, 1 - allow, 0 - denied
+static pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
+static const char *conf_mac_filter;
+
+int mac_filter_check(const uint8_t *addr)
+{
+ struct mac_t *mac;
+ int res = type;
+
+ if (type == -1)
+ return 0;
+
+ pthread_rwlock_rdlock(&lock);
+ list_for_each_entry(mac, &mac_list, entry) {
+ if (memcmp(mac->addr, addr, ETH_ALEN))
+ continue;
+ res = !type;
+ break;
+ }
+ pthread_rwlock_unlock(&lock);
+
+ return res;
+}
+
+static int mac_filter_load(const char *opt)
+{
+ struct mac_t *mac;
+ FILE *f;
+ char *c;
+ char *name = _strdup(opt);
+ char *buf = _malloc(1024);
+ int n[ETH_ALEN];
+ int i, line = 0;
+
+ c = strstr(name, ",");
+ if (!c)
+ goto err_inval;
+
+ *c = 0;
+
+ if (!strcmp(c + 1, "allow"))
+ type = 1;
+ else if (!strcmp(c + 1, "deny"))
+ type = 0;
+ else
+ goto err_inval;
+
+ f = fopen(name, "r");
+ if (!f) {
+ log_emerg("pppoe: open '%s': %s\n", name, strerror(errno));
+ goto err;
+ }
+
+ conf_mac_filter = opt;
+
+ pthread_rwlock_wrlock(&lock);
+ while (!list_empty(&mac_list)) {
+ mac = list_entry(mac_list.next, typeof(*mac), entry);
+ list_del(&mac->entry);
+ _free(mac);
+ }
+
+ while (fgets(buf, 1024, f)) {
+ line++;
+ if (buf[0] == '#' || buf[0] == ';' || buf[0] == '\n')
+ continue;
+ if (sscanf(buf, "%x:%x:%x:%x:%x:%x",
+ n + 0, n + 1, n + 2, n + 3, n + 4, n + 5) != 6) {
+ log_warn("pppoe: mac-filter:%s:%i: address is invalid\n", name, line);
+ continue;
+ }
+ mac = _malloc(sizeof(*mac));
+ for (i = 0; i < ETH_ALEN; i++) {
+ if (n[i] > 255) {
+ log_warn("pppoe: mac-filter:%s:%i: address is invalid\n", name, line);
+ _free(mac);
+ continue;
+ }
+ mac->addr[i] = n[i];
+ }
+ list_add_tail(&mac->entry, &mac_list);
+ }
+ pthread_rwlock_unlock(&lock);
+
+ fclose(f);
+
+ _free(name);
+ _free(buf);
+
+ return 0;
+
+err_inval:
+ log_emerg("pppoe: mac-filter format is invalid\n");
+err:
+ _free(name);
+ _free(buf);
+ return -1;
+}
+
+static void mac_filter_add(const char *addr, void *client)
+{
+ int n[ETH_ALEN];
+ struct mac_t *mac;
+ int i;
+
+ if (sscanf(addr, "%x:%x:%x:%x:%x:%x",
+ n + 0, n + 1, n + 2, n + 3, n + 4, n + 5) != 6) {
+ cli_send(client, "invalid format\r\n");
+ return;
+ }
+
+ mac = _malloc(sizeof(*mac));
+ for (i = 0; i < ETH_ALEN; i++) {
+ if (n[i] > 255) {
+ _free(mac);
+ cli_send(client, "invalid format\r\n");
+ return;
+ }
+ mac->addr[i] = n[i];
+ }
+
+ pthread_rwlock_wrlock(&lock);
+ list_add_tail(&mac->entry, &mac_list);
+ pthread_rwlock_unlock(&lock);
+}
+
+static void mac_filter_del(const char *addr, void *client)
+{
+ int n[ETH_ALEN];
+ uint8_t a[ETH_ALEN];
+ struct mac_t *mac;
+ int i;
+ int found = 0;
+
+ if (sscanf(addr, "%x:%x:%x:%x:%x:%x",
+ n + 0, n + 1, n + 2, n + 3, n + 4, n + 5) != 6) {
+ cli_send(client, "invalid format\r\n");
+ return;
+ }
+
+ for (i = 0; i < ETH_ALEN; i++) {
+ if (n[i] > 255) {
+ cli_send(client, "invalid format\r\n");
+ return;
+ }
+ a[i] = n[i];
+ }
+
+ pthread_rwlock_wrlock(&lock);
+ list_for_each_entry(mac, &mac_list, entry) {
+ if (memcmp(a, mac->addr, ETH_ALEN))
+ continue;
+ list_del(&mac->entry);
+ _free(mac);
+ found = 1;
+ break;
+ }
+ pthread_rwlock_unlock(&lock);
+
+ if (!found)
+ cli_send(client, "not found\r\n");
+}
+
+static void mac_filter_show(void *client)
+{
+ struct mac_t *mac;
+ const char *filter_type;
+
+ if (type == 0)
+ filter_type = "deny";
+ else if (type == 1)
+ filter_type = "allow";
+ else
+ filter_type = "disabled";
+
+ cli_sendv(client, "filter type: %s\r\n", filter_type);
+
+ pthread_rwlock_rdlock(&lock);
+ list_for_each_entry(mac, &mac_list, entry) {
+ cli_sendv(client, "%02x:%02x:%02x:%02x:%02x:%02x\r\n",
+ mac->addr[0], mac->addr[1], mac->addr[2],
+ mac->addr[3], mac->addr[4], mac->addr[5]);
+ }
+ pthread_rwlock_unlock(&lock);
+}
+
+static void cmd_help(char * const *fields, int fields_cnt, void *client);
+static int cmd_exec(const char *cmd, char * const *fields, int fields_cnt, void *client)
+{
+ if (fields_cnt == 2)
+ goto help;
+
+ if (!strcmp(fields[2], "reload")) {
+ if (!conf_mac_filter)
+ cli_send(client, "error: mac-filter was not specified in the config\r\n");
+ else if (mac_filter_load(conf_mac_filter))
+ cli_send(client, "error: check logs\r\n");
+ } else if (!strcmp(fields[2], "add")) {
+ if (fields_cnt != 4)
+ goto help;
+ mac_filter_add(fields[3], client);
+ } else if (!strcmp(fields[2], "del")) {
+ if (fields_cnt != 4)
+ goto help;
+ mac_filter_del(fields[3], client);
+ } else if (!strcmp(fields[2], "show")) {
+ mac_filter_show(client);
+ }
+
+ return CLI_CMD_OK;
+help:
+ cmd_help(fields, fields_cnt, client);
+ return CLI_CMD_OK;
+}
+
+static void cmd_help(char * const *fields, int fields_cnt, void *client)
+{
+ cli_send(client, "pppoe mac-filter reload - reload mac-filter file\r\n");
+ cli_send(client, "pppoe mac-filter add <address> - add address to mac-filter list\r\n");
+ cli_send(client, "pppoe mac-filter del <address> - delete address from mac-filter list\r\n");
+ cli_send(client, "pppoe mac-filter show - show current mac-filter list\r\n");
+}
+
+static void __init init(void)
+{
+ const char *opt = conf_get_opt("pppoe", "mac-filter");
+ if (!opt || mac_filter_load(opt))
+ type = -1;
+
+ cli_register_simple_cmd2(cmd_exec, cmd_help, 2, "pppoe", "mac-filter");
+}
+
diff --git a/accel-pppd/ctrl/pppoe/pppoe.c b/accel-pppd/ctrl/pppoe/pppoe.c
new file mode 100644
index 0000000..7860c4e
--- /dev/null
+++ b/accel-pppd/ctrl/pppoe/pppoe.c
@@ -0,0 +1,1288 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/ethernet.h>
+#include <netpacket/packet.h>
+#include <arpa/inet.h>
+#include <printf.h>
+
+#include <openssl/md5.h>
+
+#include "events.h"
+#include "triton.h"
+#include "log.h"
+#include "ppp.h"
+#include "mempool.h"
+#include "cli.h"
+
+#ifdef RADIUS
+#include "radius.h"
+#endif
+
+#include "pppoe.h"
+
+#include "memdebug.h"
+
+struct pppoe_conn_t
+{
+ struct list_head entry;
+ struct triton_context_t ctx;
+ struct pppoe_serv_t *serv;
+ int disc_sock;
+ uint16_t sid;
+ uint8_t addr[ETH_ALEN];
+ int ppp_started:1;
+
+ struct pppoe_tag *relay_sid;
+ struct pppoe_tag *host_uniq;
+ struct pppoe_tag *service_name;
+ struct pppoe_tag *tr101;
+ uint8_t cookie[COOKIE_LENGTH];
+
+ struct ppp_ctrl_t ctrl;
+ struct ppp_t ppp;
+#ifdef RADIUS
+ struct rad_plugin_t radius;
+#endif
+};
+
+struct delayed_pado_t
+{
+ struct list_head entry;
+ struct triton_timer_t timer;
+ struct pppoe_serv_t *serv;
+ uint8_t addr[ETH_ALEN];
+ struct pppoe_tag *host_uniq;
+ struct pppoe_tag *relay_sid;
+ struct pppoe_tag *service_name;
+};
+
+int conf_verbose;
+char *conf_service_name;
+char *conf_ac_name;
+int conf_ifname_in_sid;
+char *conf_pado_delay;
+
+static mempool_t conn_pool;
+static mempool_t pado_pool;
+
+unsigned int stat_active;
+unsigned int stat_delayed_pado;
+unsigned long stat_PADI_recv;
+unsigned long stat_PADO_sent;
+unsigned long stat_PADR_recv;
+unsigned long stat_PADR_dup_recv;
+unsigned long stat_PADS_sent;
+
+pthread_rwlock_t serv_lock = PTHREAD_RWLOCK_INITIALIZER;
+LIST_HEAD(serv_list);
+
+static uint8_t bc_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+static void pppoe_send_PADT(struct pppoe_conn_t *conn);
+static void _server_stop(struct pppoe_serv_t *serv);
+void pppoe_server_free(struct pppoe_serv_t *serv);
+static int init_secret(struct pppoe_serv_t *serv);
+
+static void disconnect(struct pppoe_conn_t *conn)
+{
+ if (conn->ppp_started) {
+ dpado_check_prev(__sync_fetch_and_sub(&stat_active, 1));
+ conn->ppp_started = 0;
+ ppp_terminate(&conn->ppp, TERM_USER_REQUEST, 1);
+ }
+
+ pppoe_send_PADT(conn);
+
+ close(conn->disc_sock);
+
+
+ triton_event_fire(EV_CTRL_FINISHED, &conn->ppp);
+
+ log_ppp_info1("disconnected\n");
+
+ pthread_mutex_lock(&conn->serv->lock);
+ conn->serv->conn[conn->sid] = NULL;
+ list_del(&conn->entry);
+ conn->serv->conn_cnt--;
+ if (conn->serv->stopping && conn->serv->conn_cnt == 0) {
+ pthread_mutex_unlock(&conn->serv->lock);
+ pppoe_server_free(conn->serv);
+ } else
+ pthread_mutex_unlock(&conn->serv->lock);
+
+ _free(conn->ctrl.calling_station_id);
+ _free(conn->ctrl.called_station_id);
+ _free(conn->service_name);
+ if (conn->host_uniq)
+ _free(conn->host_uniq);
+ if (conn->relay_sid)
+ _free(conn->relay_sid);
+
+ triton_context_unregister(&conn->ctx);
+
+ mempool_free(conn);
+}
+
+static void ppp_started(struct ppp_t *ppp)
+{
+ log_ppp_debug("pppoe: ppp started\n");
+}
+
+static void ppp_finished(struct ppp_t *ppp)
+{
+ struct pppoe_conn_t *conn = container_of(ppp, typeof(*conn), ppp);
+
+ log_ppp_debug("pppoe: ppp finished\n");
+
+ if (conn->ppp_started) {
+ dpado_check_prev(__sync_fetch_and_sub(&stat_active, 1));
+ conn->ppp_started = 0;
+ triton_context_call(&conn->ctx, (triton_event_func)disconnect, conn);
+ }
+}
+
+static void pppoe_conn_close(struct triton_context_t *ctx)
+{
+ struct pppoe_conn_t *conn = container_of(ctx, typeof(*conn), ctx);
+
+ if (conn->ppp_started)
+ ppp_terminate(&conn->ppp, TERM_ADMIN_RESET, 0);
+ else
+ disconnect(conn);
+}
+
+#ifdef RADIUS
+static int pppoe_rad_send_access_request(struct rad_plugin_t *rad, struct rad_packet_t *pack)
+{
+ struct pppoe_conn_t *conn = container_of(rad, typeof(*conn), radius);
+
+ if (conn->tr101)
+ return tr101_send_access_request(conn->tr101, pack);
+
+ return 0;
+}
+
+static int pppoe_rad_send_accounting_request(struct rad_plugin_t *rad, struct rad_packet_t *pack)
+{
+ struct pppoe_conn_t *conn = container_of(rad, typeof(*conn), radius);
+
+ if (conn->tr101)
+ return tr101_send_accounting_request(conn->tr101, pack);
+
+ return 0;
+}
+#endif
+
+static struct pppoe_conn_t *allocate_channel(struct pppoe_serv_t *serv, const uint8_t *addr, const struct pppoe_tag *host_uniq, const struct pppoe_tag *relay_sid, const struct pppoe_tag *service_name, const struct pppoe_tag *tr101, const uint8_t *cookie)
+{
+ struct pppoe_conn_t *conn;
+ int sid;
+
+ conn = mempool_alloc(conn_pool);
+ if (!conn) {
+ log_emerg("pppoe: out of memory\n");
+ return NULL;
+ }
+
+ memset(conn, 0, sizeof(*conn));
+
+ pthread_mutex_lock(&serv->lock);
+ for (sid = serv->sid + 1; sid != serv->sid; sid++) {
+ if (sid == MAX_SID)
+ sid = 1;
+ if (!serv->conn[sid]) {
+ conn->sid = sid;
+ serv->sid = sid;
+ serv->conn[sid] = conn;
+ list_add_tail(&conn->entry, &serv->conn_list);
+ serv->conn_cnt++;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&serv->lock);
+
+ if (!conn->sid) {
+ log_warn("pppoe: no free sid available\n");
+ mempool_free(conn);
+ return NULL;
+ }
+
+ conn->serv = serv;
+ memcpy(conn->addr, addr, ETH_ALEN);
+
+ if (host_uniq) {
+ conn->host_uniq = _malloc(sizeof(*host_uniq) + ntohs(host_uniq->tag_len));
+ memcpy(conn->host_uniq, host_uniq, sizeof(*host_uniq) + ntohs(host_uniq->tag_len));
+ }
+
+ if (relay_sid) {
+ conn->relay_sid = _malloc(sizeof(*relay_sid) + ntohs(relay_sid->tag_len));
+ memcpy(conn->relay_sid, relay_sid, sizeof(*relay_sid) + ntohs(relay_sid->tag_len));
+ }
+
+ if (tr101) {
+ conn->tr101 = _malloc(sizeof(*tr101) + ntohs(tr101->tag_len));
+ memcpy(conn->tr101, tr101, sizeof(*tr101) + ntohs(tr101->tag_len));
+ }
+
+ conn->service_name = _malloc(sizeof(*service_name) + ntohs(service_name->tag_len));
+ memcpy(conn->service_name, service_name, sizeof(*service_name) + ntohs(service_name->tag_len));
+
+ memcpy(conn->cookie, cookie, COOKIE_LENGTH);
+
+ conn->ctx.before_switch = log_switch;
+ conn->ctx.close = pppoe_conn_close;
+ conn->ctrl.ctx = &conn->ctx;
+ conn->ctrl.started = ppp_started;
+ conn->ctrl.finished = ppp_finished;
+ conn->ctrl.max_mtu = MAX_PPPOE_MTU;
+ conn->ctrl.name = "pppoe";
+
+ conn->ctrl.calling_station_id = _malloc(IFNAMSIZ + 19);
+ conn->ctrl.called_station_id = _malloc(IFNAMSIZ + 19);
+
+ if (conf_ifname_in_sid == 1 || conf_ifname_in_sid == 3)
+ sprintf(conn->ctrl.calling_station_id, "%s:%02x:%02x:%02x:%02x:%02x:%02x", serv->ifname,
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+ else
+ sprintf(conn->ctrl.calling_station_id, "%02x:%02x:%02x:%02x:%02x:%02x",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+
+ if (conf_ifname_in_sid == 2 || conf_ifname_in_sid == 3)
+ sprintf(conn->ctrl.called_station_id, "%s:%02x:%02x:%02x:%02x:%02x:%02x", serv->ifname,
+ serv->hwaddr[0], serv->hwaddr[1], serv->hwaddr[2], serv->hwaddr[3], serv->hwaddr[4], serv->hwaddr[5]);
+ else
+ sprintf(conn->ctrl.called_station_id, "%02x:%02x:%02x:%02x:%02x:%02x",
+ serv->hwaddr[0], serv->hwaddr[1], serv->hwaddr[2], serv->hwaddr[3], serv->hwaddr[4], serv->hwaddr[5]);
+
+ ppp_init(&conn->ppp);
+
+ conn->ppp.ctrl = &conn->ctrl;
+ conn->ppp.chan_name = conn->ctrl.calling_station_id;
+
+ triton_context_register(&conn->ctx, &conn->ppp);
+ triton_context_wakeup(&conn->ctx);
+
+ triton_event_fire(EV_CTRL_STARTING, &conn->ppp);
+ triton_event_fire(EV_CTRL_STARTED, &conn->ppp);
+
+ conn->disc_sock = dup(serv->hnd.fd);
+
+ return conn;
+}
+
+static void connect_channel(struct pppoe_conn_t *conn)
+{
+ int sock;
+ struct sockaddr_pppox sp;
+
+ sock = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_OE);
+ if (!sock) {
+ log_error("pppoe: socket(PPPOX): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ memset(&sp, 0, sizeof(sp));
+
+ sp.sa_family = AF_PPPOX;
+ sp.sa_protocol = PX_PROTO_OE;
+ sp.sa_addr.pppoe.sid = htons(conn->sid);
+ strcpy(sp.sa_addr.pppoe.dev, conn->serv->ifname);
+ memcpy(sp.sa_addr.pppoe.remote, conn->addr, ETH_ALEN);
+
+ if (connect(sock, (struct sockaddr *)&sp, sizeof(sp))) {
+ log_error("pppoe: connect: %s\n", strerror(errno));
+ goto out_err_close;
+ }
+
+ conn->ppp.fd = sock;
+
+ if (establish_ppp(&conn->ppp))
+ goto out_err_close;
+
+#ifdef RADIUS
+ if (conn->tr101) {
+ conn->radius.send_access_request = pppoe_rad_send_access_request;
+ conn->radius.send_accounting_request = pppoe_rad_send_accounting_request;
+ rad_register_plugin(&conn->ppp, &conn->radius);
+ }
+#endif
+
+ conn->ppp_started = 1;
+
+ dpado_check_next(__sync_add_and_fetch(&stat_active, 1));
+
+ return;
+
+out_err_close:
+ close(sock);
+out_err:
+ disconnect(conn);
+}
+
+static struct pppoe_conn_t *find_channel(struct pppoe_serv_t *serv, const uint8_t *cookie)
+{
+ struct pppoe_conn_t *conn;
+
+ list_for_each_entry(conn, &serv->conn_list, entry)
+ if (!memcmp(conn->cookie, cookie, COOKIE_LENGTH))
+ return conn;
+
+ return NULL;
+}
+
+static void print_tag_string(struct pppoe_tag *tag)
+{
+ int i;
+
+ for (i = 0; i < ntohs(tag->tag_len); i++)
+ log_info2("%c", tag->tag_data[i]);
+}
+
+static void print_tag_octets(struct pppoe_tag *tag)
+{
+ int i;
+
+ for (i = 0; i < ntohs(tag->tag_len); i++)
+ log_info2("%02x", (uint8_t)tag->tag_data[i]);
+}
+
+static void print_packet(uint8_t *pack)
+{
+ struct ethhdr *ethhdr = (struct ethhdr *)pack;
+ struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN);
+ struct pppoe_tag *tag;
+ int n;
+
+ log_info2("[PPPoE ");
+
+ switch (hdr->code) {
+ case CODE_PADI:
+ log_info2("PADI");
+ break;
+ case CODE_PADO:
+ log_info2("PADO");
+ break;
+ case CODE_PADR:
+ log_info2("PADR");
+ break;
+ case CODE_PADS:
+ log_info2("PADS");
+ break;
+ case CODE_PADT:
+ log_info2("PADT");
+ break;
+ }
+
+ log_info2(" %02x:%02x:%02x:%02x:%02x:%02x => %02x:%02x:%02x:%02x:%02x:%02x",
+ ethhdr->h_source[0], ethhdr->h_source[1], ethhdr->h_source[2], ethhdr->h_source[3], ethhdr->h_source[4], ethhdr->h_source[5],
+ ethhdr->h_dest[0], ethhdr->h_dest[1], ethhdr->h_dest[2], ethhdr->h_dest[3], ethhdr->h_dest[4], ethhdr->h_dest[5]);
+
+ log_info2(" sid=%04x", ntohs(hdr->sid));
+
+ 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:
+ log_info2(" <End-Of-List>");
+ break;
+ case TAG_SERVICE_NAME:
+ log_info2(" <Service-Name ");
+ print_tag_string(tag);
+ log_info2(">");
+ break;
+ case TAG_AC_NAME:
+ log_info2(" <AC-Name ");
+ print_tag_string(tag);
+ log_info2(">");
+ break;
+ case TAG_HOST_UNIQ:
+ log_info2(" <Host-Uniq ");
+ print_tag_octets(tag);
+ log_info2(">");
+ break;
+ case TAG_AC_COOKIE:
+ log_info2(" <AC-Cookie ");
+ print_tag_octets(tag);
+ log_info2(">");
+ break;
+ case TAG_VENDOR_SPECIFIC:
+ if (ntohs(tag->tag_len) < 4)
+ log_info2(" <Vendor-Specific invalid>");
+ else
+ log_info2(" <Vendor-Specific %x>", ntohl(*(uint32_t *)tag->tag_data));
+ break;
+ case TAG_RELAY_SESSION_ID:
+ log_info2(" <Relay-Session-Id");
+ print_tag_octets(tag);
+ log_info2(">");
+ break;
+ case TAG_SERVICE_NAME_ERROR:
+ log_info2(" <Service-Name-Error>");
+ break;
+ case TAG_AC_SYSTEM_ERROR:
+ log_info2(" <AC-System-Error>");
+ break;
+ case TAG_GENERIC_ERROR:
+ log_info2(" <Generic-Error>");
+ break;
+ default:
+ log_info2(" <Unknown (%x)>", ntohs(tag->tag_type));
+ break;
+ }
+ }
+
+ log_info2("]\n");
+}
+
+static void generate_cookie(struct pppoe_serv_t *serv, const uint8_t *src, uint8_t *cookie)
+{
+ MD5_CTX ctx;
+ DES_cblock key;
+ DES_key_schedule ks;
+ int i;
+ union {
+ DES_cblock b[3];
+ uint8_t raw[24];
+ } u1, u2;
+
+ memset(&key, 0, sizeof(key));
+ DES_random_key(&key);
+ DES_set_key(&key, &ks);
+
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, serv->secret, SECRET_LENGTH);
+ MD5_Update(&ctx, serv->hwaddr, ETH_ALEN);
+ MD5_Update(&ctx, src, ETH_ALEN);
+ MD5_Update(&ctx, &key, 8);
+ MD5_Final(u1.raw, &ctx);
+
+ for (i = 0; i < 2; i++)
+ DES_ecb_encrypt(&u1.b[i], &u2.b[i], &ks, DES_ENCRYPT);
+ memcpy(u2.b[2], &key, 8);
+
+ for (i = 0; i < 3; i++)
+ DES_ecb_encrypt(&u2.b[i], &u1.b[i], &serv->des_ks, DES_ENCRYPT);
+
+ memcpy(cookie, u1.raw, 24);
+}
+
+static int check_cookie(struct pppoe_serv_t *serv, const uint8_t *src, const uint8_t *cookie)
+{
+ MD5_CTX ctx;
+ DES_key_schedule ks;
+ int i;
+ union {
+ DES_cblock b[3];
+ uint8_t raw[24];
+ } u1, u2;
+
+ memcpy(u1.raw, cookie, 24);
+
+ for (i = 0; i < 3; i++)
+ DES_ecb_encrypt(&u1.b[i], &u2.b[i], &serv->des_ks, DES_DECRYPT);
+
+ if (DES_set_key_checked(&u2.b[2], &ks))
+ return -1;
+
+ for (i = 0; i < 2; i++)
+ DES_ecb_encrypt(&u2.b[i], &u1.b[i], &ks, DES_DECRYPT);
+
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, serv->secret, SECRET_LENGTH);
+ MD5_Update(&ctx, serv->hwaddr, ETH_ALEN);
+ MD5_Update(&ctx, src, ETH_ALEN);
+ MD5_Update(&ctx, u2.b[2], 8);
+ MD5_Final(u2.raw, &ctx);
+
+ return memcmp(u1.raw, u2.raw, 16);
+}
+
+static void setup_header(uint8_t *pack, const uint8_t *src, const uint8_t *dst, int code, uint16_t sid)
+{
+ 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(ETH_P_PPP_DISC);
+
+ hdr->ver = 1;
+ hdr->type = 1;
+ hdr->code = code;
+ hdr->sid = htons(sid);
+ hdr->length = 0;
+}
+
+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_send(int fd, const uint8_t *pack)
+{
+ struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN);
+ int n, s;
+
+ s = ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length);
+ n = write(fd, pack, s);
+ if (n < 0 )
+ log_error("pppoe: write: %s\n", strerror(errno));
+ else if (n != s) {
+ log_warn("pppoe: short write %i/%i\n", n,s);
+ }
+}
+
+static void pppoe_send_PADO(struct pppoe_serv_t *serv, 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];
+ uint8_t cookie[COOKIE_LENGTH];
+
+ setup_header(pack, serv->hwaddr, addr, CODE_PADO, 0);
+
+ add_tag(pack, TAG_AC_NAME, (uint8_t *)conf_ac_name, strlen(conf_ac_name));
+ if (conf_service_name)
+ add_tag(pack, TAG_SERVICE_NAME, (uint8_t *)conf_service_name, strlen(conf_service_name));
+
+ if (service_name)
+ add_tag2(pack, service_name);
+
+ generate_cookie(serv, addr, cookie);
+ add_tag(pack, TAG_AC_COOKIE, cookie, COOKIE_LENGTH);
+
+ if (host_uniq)
+ add_tag2(pack, host_uniq);
+
+ if (relay_sid)
+ add_tag2(pack, relay_sid);
+
+ if (conf_verbose) {
+ log_info2("send ");
+ print_packet(pack);
+ }
+
+ __sync_add_and_fetch(&stat_PADO_sent, 1);
+ pppoe_send(serv->hnd.fd, pack);
+}
+
+static void pppoe_send_err(struct pppoe_serv_t *serv, 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, serv->hwaddr, addr, code, 0);
+
+ add_tag(pack, TAG_AC_NAME, (uint8_t *)conf_ac_name, strlen(conf_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);
+
+ if (conf_verbose) {
+ log_info2("send ");
+ print_packet(pack);
+ }
+
+ pppoe_send(serv->hnd.fd, pack);
+}
+
+static void pppoe_send_PADS(struct pppoe_conn_t *conn)
+{
+ uint8_t pack[ETHER_MAX_LEN];
+
+ setup_header(pack, conn->serv->hwaddr, conn->addr, CODE_PADS, conn->sid);
+
+ add_tag(pack, TAG_AC_NAME, (uint8_t *)conf_ac_name, strlen(conf_ac_name));
+
+ add_tag2(pack, conn->service_name);
+
+ if (conn->host_uniq)
+ add_tag2(pack, conn->host_uniq);
+
+ if (conn->relay_sid)
+ add_tag2(pack, conn->relay_sid);
+
+ if (conf_verbose) {
+ log_info2("send ");
+ print_packet(pack);
+ }
+
+ __sync_add_and_fetch(&stat_PADS_sent, 1);
+ pppoe_send(conn->disc_sock, pack);
+}
+
+static void pppoe_send_PADT(struct pppoe_conn_t *conn)
+{
+ uint8_t pack[ETHER_MAX_LEN];
+
+ setup_header(pack, conn->serv->hwaddr, conn->addr, CODE_PADT, conn->sid);
+
+ add_tag(pack, TAG_AC_NAME, (uint8_t *)conf_ac_name, strlen(conf_ac_name));
+
+ add_tag2(pack, conn->service_name);
+
+ if (conn->host_uniq)
+ add_tag2(pack, conn->host_uniq);
+
+ if (conn->relay_sid)
+ add_tag2(pack, conn->relay_sid);
+
+ if (conf_verbose) {
+ log_info2("send ");
+ print_packet(pack);
+ }
+
+ pppoe_send(conn->disc_sock, pack);
+}
+
+static void free_delayed_pado(struct delayed_pado_t *pado)
+{
+ triton_timer_del(&pado->timer);
+
+ __sync_sub_and_fetch(&stat_delayed_pado, 1);
+ list_del(&pado->entry);
+
+ if (pado->host_uniq)
+ _free(pado->host_uniq);
+ if (pado->relay_sid)
+ _free(pado->relay_sid);
+ if (pado->service_name)
+ _free(pado->service_name);
+
+ mempool_free(pado);
+}
+
+static void pado_timer(struct triton_timer_t *t)
+{
+ struct delayed_pado_t *pado = container_of(t, typeof(*pado), timer);
+
+ if (!ppp_shutdown)
+ pppoe_send_PADO(pado->serv, pado->addr, pado->host_uniq, pado->relay_sid, pado->service_name);
+
+ free_delayed_pado(pado);
+}
+
+static void pppoe_recv_PADI(struct pppoe_serv_t *serv, 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;
+ struct delayed_pado_t *pado;
+
+ __sync_add_and_fetch(&stat_PADI_recv, 1);
+
+ if (ppp_shutdown || pado_delay == -1)
+ return;
+
+ if (hdr->sid) {
+ log_warn("pppoe: discarding PADI packet (sid is not zero)\n");
+ return;
+ }
+
+ if (conf_verbose) {
+ log_info2("recv ");
+ print_packet(pack);
+ }
+
+ 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:
+ if (conf_service_name && tag->tag_len) {
+ if (ntohs(tag->tag_len) != strlen(conf_service_name))
+ break;
+ if (memcmp(tag->tag_data, conf_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) {
+ if (conf_verbose)
+ log_warn("pppoe: discarding PADI packet (Service-Name mismatch)\n");
+ return;
+ }
+
+ if (pado_delay) {
+ list_for_each_entry(pado, &serv->pado_list, entry) {
+ if (memcmp(pado->addr, ethhdr->h_source, ETH_ALEN))
+ continue;
+ if (conf_verbose)
+ log_warn("pppoe: discarding PADI packet (already queued)\n");
+ return;
+ }
+ pado = mempool_alloc(pado_pool);
+ memset(pado, 0, sizeof(*pado));
+ pado->serv = serv;
+ memcpy(pado->addr, ethhdr->h_source, ETH_ALEN);
+
+ if (host_uniq_tag) {
+ pado->host_uniq = _malloc(sizeof(*host_uniq_tag) + ntohs(host_uniq_tag->tag_len));
+ memcpy(pado->host_uniq, host_uniq_tag, sizeof(*host_uniq_tag) + ntohs(host_uniq_tag->tag_len));
+ }
+
+ if (relay_sid_tag) {
+ pado->relay_sid = _malloc(sizeof(*relay_sid_tag) + ntohs(relay_sid_tag->tag_len));
+ memcpy(pado->relay_sid, relay_sid_tag, sizeof(*relay_sid_tag) + ntohs(relay_sid_tag->tag_len));
+ }
+
+ if (service_name_tag) {
+ pado->service_name = _malloc(sizeof(*service_name_tag) + ntohs(service_name_tag->tag_len));
+ memcpy(pado->service_name, service_name_tag, sizeof(*service_name_tag) + ntohs(service_name_tag->tag_len));
+ }
+
+ pado->timer.expire = pado_timer;
+ pado->timer.period = pado_delay;
+
+ triton_timer_add(&serv->ctx, &pado->timer, 0);
+
+ list_add_tail(&pado->entry, &serv->pado_list);
+ __sync_add_and_fetch(&stat_delayed_pado, 1);
+ } else
+ pppoe_send_PADO(serv, ethhdr->h_source, host_uniq_tag, relay_sid_tag, service_name_tag);
+}
+
+static void pppoe_recv_PADR(struct pppoe_serv_t *serv, 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;
+ struct pppoe_tag *tr101_tag = NULL;
+ int n, service_match = 0;
+ struct pppoe_conn_t *conn;
+ int vendor_id;
+
+ __sync_add_and_fetch(&stat_PADR_recv, 1);
+
+ if (ppp_shutdown)
+ return;
+
+ if (!memcmp(ethhdr->h_dest, bc_addr, ETH_ALEN)) {
+ if (conf_verbose)
+ log_warn("pppoe: discard PADR (destination address is broadcast)\n");
+ return;
+ }
+
+ if (hdr->sid) {
+ if (conf_verbose)
+ log_warn("pppoe: discarding PADR packet (sid is not zero)\n");
+ return;
+ }
+
+ if (conf_verbose) {
+ log_info2("recv ");
+ print_packet(pack);
+ }
+
+ 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 (conf_service_name) {
+ if (ntohs(tag->tag_len) != strlen(conf_service_name))
+ break;
+ if (memcmp(tag->tag_data, conf_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;
+ case TAG_VENDOR_SPECIFIC:
+ if (ntohs(tag->tag_len) < 4)
+ continue;
+ vendor_id = ntohl(*(uint32_t *)tag->tag_data);
+ if (vendor_id == VENDOR_ADSL_FORUM)
+ tr101_tag = tag;
+ break;
+ }
+ }
+
+ if (!ac_cookie_tag) {
+ if (conf_verbose)
+ log_warn("pppoe: discard PADR packet (no AC-Cookie tag present)\n");
+ return;
+ }
+
+ if (ntohs(ac_cookie_tag->tag_len) != COOKIE_LENGTH) {
+ if (conf_verbose)
+ log_warn("pppoe: discard PADR packet (incorrect AC-Cookie tag length)\n");
+ return;
+ }
+
+ if (check_cookie(serv, ethhdr->h_source, (uint8_t *)ac_cookie_tag->tag_data)) {
+ if (conf_verbose)
+ log_warn("pppoe: discard PADR packet (incorrect AC-Cookie)\n");
+ return;
+ }
+
+ if (!service_match) {
+ if (conf_verbose)
+ log_warn("pppoe: Service-Name mismatch\n");
+ pppoe_send_err(serv, ethhdr->h_source, host_uniq_tag, relay_sid_tag, CODE_PADS, TAG_SERVICE_NAME_ERROR);
+ return;
+ }
+
+ pthread_mutex_lock(&serv->lock);
+ conn = find_channel(serv, (uint8_t *)ac_cookie_tag->tag_data);
+ if (conn && !conn->ppp.username) {
+ __sync_add_and_fetch(&stat_PADR_dup_recv, 1);
+ pppoe_send_PADS(conn);
+ }
+ pthread_mutex_unlock(&serv->lock);
+
+ if (conn)
+ return;
+
+ conn = allocate_channel(serv, ethhdr->h_source, host_uniq_tag, relay_sid_tag, service_name_tag, tr101_tag, (uint8_t *)ac_cookie_tag->tag_data);
+ if (!conn)
+ pppoe_send_err(serv, ethhdr->h_source, host_uniq_tag, relay_sid_tag, CODE_PADS, TAG_AC_SYSTEM_ERROR);
+ else {
+ pppoe_send_PADS(conn);
+ triton_context_call(&conn->ctx, (triton_event_func)connect_channel, conn);
+ }
+}
+
+static void pppoe_recv_PADT(struct pppoe_serv_t *serv, uint8_t *pack)
+{
+ struct ethhdr *ethhdr = (struct ethhdr *)pack;
+ struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN);
+ struct pppoe_conn_t *conn;
+
+ if (!memcmp(ethhdr->h_dest, bc_addr, ETH_ALEN)) {
+ if (conf_verbose)
+ log_warn("pppoe: discard PADT (destination address is broadcast)\n");
+ return;
+ }
+
+ if (conf_verbose) {
+ log_info2("recv ");
+ print_packet(pack);
+ }
+
+ pthread_mutex_lock(&serv->lock);
+ conn = serv->conn[ntohs(hdr->sid)];
+ if (conn && !memcmp(conn->addr, ethhdr->h_source, ETH_ALEN))
+ triton_context_call(&conn->ctx, (void (*)(void *))disconnect, conn);
+ pthread_mutex_unlock(&serv->lock);
+}
+
+static int pppoe_serv_read(struct triton_md_handler_t *h)
+{
+ struct pppoe_serv_t *serv = container_of(h, typeof(*serv), hnd);
+ uint8_t pack[ETHER_MAX_LEN];
+ struct ethhdr *ethhdr = (struct ethhdr *)pack;
+ struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN);
+ int n;
+
+ while (1) {
+ n = read(h->fd, pack, sizeof(pack));
+ if (n < 0) {
+ if (errno == EAGAIN)
+ break;
+ log_error("pppoe: read: %s\n", strerror(errno));
+ return 0;
+ }
+
+ if (n < ETH_HLEN + sizeof(*hdr)) {
+ if (conf_verbose)
+ log_warn("pppoe: short packet received (%i)\n", n);
+ continue;
+ }
+
+ if (mac_filter_check(ethhdr->h_source))
+ continue;
+
+ if (memcmp(ethhdr->h_dest, bc_addr, ETH_ALEN) && memcmp(ethhdr->h_dest, serv->hwaddr, ETH_ALEN))
+ continue;
+
+ if (!memcmp(ethhdr->h_source, bc_addr, ETH_ALEN)) {
+ if (conf_verbose)
+ log_warn("pppoe: discarding packet (host address is broadcast)\n");
+ continue;
+ }
+
+ if ((ethhdr->h_source[0] & 1) != 0) {
+ if (conf_verbose)
+ log_warn("pppoe: discarding packet (host address is not unicast)\n");
+ continue;
+ }
+
+ if (n < ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length)) {
+ if (conf_verbose)
+ log_warn("pppoe: short packet received\n");
+ continue;
+ }
+
+ if (hdr->ver != 1) {
+ if (conf_verbose)
+ log_warn("pppoe: discarding packet (unsupported version %i)\n", hdr->ver);
+ continue;
+ }
+
+ if (hdr->type != 1) {
+ if (conf_verbose)
+ log_warn("pppoe: discarding packet (unsupported type %i)\n", hdr->type);
+ }
+
+ switch (hdr->code) {
+ case CODE_PADI:
+ pppoe_recv_PADI(serv, pack, n);
+ break;
+ case CODE_PADR:
+ pppoe_recv_PADR(serv, pack, n);
+ break;
+ case CODE_PADT:
+ pppoe_recv_PADT(serv, pack);
+ break;
+ }
+ }
+ return 0;
+}
+
+static void pppoe_serv_close(struct triton_context_t *ctx)
+{
+ struct pppoe_serv_t *serv = container_of(ctx, typeof(*serv), ctx);
+
+ triton_md_disable_handler(&serv->hnd, MD_MODE_READ | MD_MODE_WRITE);
+
+ serv->stopping = 1;
+
+ pthread_mutex_lock(&serv->lock);
+ if (!serv->conn_cnt) {
+ pthread_mutex_unlock(&serv->lock);
+ pppoe_server_free(serv);
+ return;
+ }
+ pthread_mutex_unlock(&serv->lock);
+}
+
+void pppoe_server_start(const char *ifname, void *cli)
+{
+ struct pppoe_serv_t *serv;
+ int sock;
+ int opt = 1;
+ struct ifreq ifr;
+ struct sockaddr_ll sa;
+
+ pthread_rwlock_rdlock(&serv_lock);
+ list_for_each_entry(serv, &serv_list, entry) {
+ if (!strcmp(serv->ifname, ifname)) {
+ if (cli)
+ cli_send(cli, "error: already exists\r\n");
+ pthread_rwlock_unlock(&serv_lock);
+ return;
+ }
+ }
+ pthread_rwlock_unlock(&serv_lock);
+
+ serv = _malloc(sizeof(*serv));
+ memset(serv, 0, sizeof(*serv));
+
+ if (init_secret(serv)) {
+ if (cli)
+ cli_sendv(cli, "init secret failed\r\n");
+ _free(serv);
+ return;
+ }
+
+ sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_PPP_DISC));
+ if (sock < 0) {
+ if (cli)
+ cli_sendv(cli, "socket: %s\r\n", strerror(errno));
+ log_emerg("pppoe: socket: %s\n", strerror(errno));
+ _free(serv);
+ return;
+ }
+
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt))) {
+ if (cli)
+ cli_sendv(cli, "setsockopt(SO_BROADCAST): %s\r\n", strerror(errno));
+ log_emerg("pppoe: setsockopt(SO_BROADCAST): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(sock, SIOCGIFHWADDR, &ifr)) {
+ if (cli)
+ cli_sendv(cli, "ioctl(SIOCGIFHWADDR): %s\r\n", strerror(errno));
+ log_emerg("pppoe: ioctl(SIOCGIFHWADDR): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+#ifdef ARPHDR_ETHER
+ if (ifr.ifr_hwaddr.sa_family != ARPHDR_ETHER) {
+ log_emerg("pppoe: interface %s is not ethernet\n", ifname);
+ goto out_err;
+ }
+#endif
+
+ if ((ifr.ifr_hwaddr.sa_data[0] & 1) != 0) {
+ if (cli)
+ cli_sendv(cli, "interface %s has not unicast address\r\n", ifname);
+ log_emerg("pppoe: interface %s has not unicast address\n", ifname);
+ goto out_err;
+ }
+
+ memcpy(serv->hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
+
+ if (ioctl(sock, SIOCGIFMTU, &ifr)) {
+ if (cli)
+ cli_sendv(cli, "ioctl(SIOCGIFMTU): %s\r\n", strerror(errno));
+ log_emerg("pppoe: ioctl(SIOCGIFMTU): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ if (ifr.ifr_mtu < ETH_DATA_LEN) {
+ if (cli)
+ cli_sendv(cli, "interface %s has MTU of %i, should be %i\r\n", ifname, ifr.ifr_mtu, ETH_DATA_LEN);
+ log_emerg("pppoe: interface %s has MTU of %i, should be %i\n", ifname, ifr.ifr_mtu, ETH_DATA_LEN);
+ }
+
+ if (ioctl(sock, SIOCGIFINDEX, &ifr)) {
+ if (cli)
+ cli_sendv(cli, "ioctl(SIOCGIFINDEX): %s\r\n", strerror(errno));
+ log_emerg("pppoe: ioctl(SIOCGIFINDEX): %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ 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(sock, (struct sockaddr *)&sa, sizeof(sa))) {
+ if (cli)
+ cli_sendv(cli, "bind: %s\n", strerror(errno));
+ log_emerg("pppoe: bind: %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ if (fcntl(sock, F_SETFL, O_NONBLOCK)) {
+ if (cli)
+ cli_sendv(cli, "failed to set nonblocking mode: %s\n", strerror(errno));
+ log_emerg("pppoe: failed to set nonblocking mode: %s\n", strerror(errno));
+ goto out_err;
+ }
+
+ serv->ctx.close = pppoe_serv_close;
+ serv->ctx.before_switch = log_switch;
+ serv->hnd.fd = sock;
+ serv->hnd.read = pppoe_serv_read;
+ serv->ifname = _strdup(ifname);
+ pthread_mutex_init(&serv->lock, NULL);
+
+ INIT_LIST_HEAD(&serv->conn_list);
+ INIT_LIST_HEAD(&serv->pado_list);
+
+ triton_context_register(&serv->ctx, NULL);
+ triton_md_register_handler(&serv->ctx, &serv->hnd);
+ triton_md_enable_handler(&serv->hnd, MD_MODE_READ);
+ triton_context_wakeup(&serv->ctx);
+
+ pthread_rwlock_wrlock(&serv_lock);
+ list_add_tail(&serv->entry, &serv_list);
+ pthread_rwlock_unlock(&serv_lock);
+
+ return;
+
+out_err:
+ close(sock);
+ _free(serv);
+}
+
+static void _conn_stop(struct pppoe_conn_t *conn)
+{
+ ppp_terminate(&conn->ppp, 0, TERM_ADMIN_RESET);
+}
+
+static void _server_stop(struct pppoe_serv_t *serv)
+{
+ struct pppoe_conn_t *conn;
+
+ if (serv->stopping)
+ return;
+
+ serv->stopping = 1;
+ triton_md_disable_handler(&serv->hnd, MD_MODE_READ | MD_MODE_WRITE);
+
+ pthread_mutex_lock(&serv->lock);
+ if (!serv->conn_cnt) {
+ pthread_mutex_unlock(&serv->lock);
+ pppoe_server_free(serv);
+ return;
+ }
+ list_for_each_entry(conn, &serv->conn_list, entry)
+ triton_context_call(&conn->ctx, (triton_event_func)_conn_stop, conn);
+ pthread_mutex_unlock(&serv->lock);
+}
+
+void pppoe_server_free(struct pppoe_serv_t *serv)
+{
+ struct delayed_pado_t *pado;
+
+ pthread_rwlock_wrlock(&serv_lock);
+ list_del(&serv->entry);
+ pthread_rwlock_unlock(&serv_lock);
+
+ while (!list_empty(&serv->pado_list)) {
+ pado = list_entry(serv->pado_list.next, typeof(*pado), entry);
+ free_delayed_pado(pado);
+ }
+
+ triton_md_unregister_handler(&serv->hnd);
+ close(serv->hnd.fd);
+ triton_context_unregister(&serv->ctx);
+ _free(serv->ifname);
+ _free(serv);
+}
+
+void pppoe_server_stop(const char *ifname)
+{
+ struct pppoe_serv_t *serv;
+
+ pthread_rwlock_rdlock(&serv_lock);
+ list_for_each_entry(serv, &serv_list, entry) {
+ if (strcmp(serv->ifname, ifname))
+ continue;
+ triton_context_call(&serv->ctx, (triton_event_func)_server_stop, serv);
+ break;
+ }
+ pthread_rwlock_unlock(&serv_lock);
+}
+
+static int init_secret(struct pppoe_serv_t *serv)
+{
+ int fd;
+ DES_cblock key;
+
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ log_emerg("pppoe: cann't open /dev/urandom: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (read(fd, serv->secret, SECRET_LENGTH) < 0) {
+ log_emerg("pppoe: faild to read /dev/urandom\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+
+ memset(key, 0, sizeof(key));
+ DES_random_key(&key);
+ DES_set_key(&key, &serv->des_ks);
+
+ return 0;
+}
+
+static void __init pppoe_init(void)
+{
+ struct conf_sect_t *s = conf_get_section("pppoe");
+ struct conf_option_t *opt;
+
+ conn_pool = mempool_create(sizeof(struct pppoe_conn_t));
+ pado_pool = mempool_create(sizeof(struct delayed_pado_t));
+
+ if (!s) {
+ log_emerg("pppoe: no configuration, disabled...\n");
+ return;
+ }
+
+ list_for_each_entry(opt, &s->items, entry) {
+ if (!strcmp(opt->name, "interface")) {
+ if (opt->val)
+ pppoe_server_start(opt->val, NULL);
+ } else if (!strcmp(opt->name, "verbose")) {
+ if (atoi(opt->val) > 0)
+ conf_verbose = 1;
+ } else if (!strcmp(opt->name, "ac-name") || !strcmp(opt->name, "AC-Name")) {
+ if (opt->val && strlen(opt->val))
+ conf_ac_name = _strdup(opt->val);
+ } else if (!strcmp(opt->name, "service-name") || !strcmp(opt->name, "Service-Name")) {
+ if (opt->val && strlen(opt->val))
+ conf_service_name = _strdup(opt->val);
+ } else if (!strcmp(opt->name, "pado-delay") || !strcmp(opt->name, "PADO-delay")) {
+ if (dpado_parse(opt->val))
+ _exit(EXIT_FAILURE);
+ } else if (!strcmp(opt->name, "ifname-in-sid")) {
+ if (!opt->val)
+ continue;
+ if (!strcmp(opt->val, "called-sid"))
+ conf_ifname_in_sid = 1;
+ else if (!strcmp(opt->val, "calling-sid"))
+ conf_ifname_in_sid = 2;
+ else if (!strcmp(opt->val, "both"))
+ conf_ifname_in_sid = 3;
+ else if (atoi(opt->val) >= 0)
+ conf_ifname_in_sid = atoi(opt->val);
+ }
+ }
+
+ if (!conf_ac_name)
+ conf_ac_name = _strdup("accel-ppp");
+}
+
diff --git a/accel-pppd/ctrl/pppoe/pppoe.h b/accel-pppd/ctrl/pppoe/pppoe.h
new file mode 100644
index 0000000..2264dd1
--- /dev/null
+++ b/accel-pppd/ctrl/pppoe/pppoe.h
@@ -0,0 +1,117 @@
+#ifndef __PPPOE_H
+#define __PPPOE_H
+
+#include <pthread.h>
+
+#include <openssl/des.h>
+
+#include <linux/if.h>
+#include <linux/if_pppox.h>
+
+/* 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
+
+/* Discovery phase states */
+#define STATE_SENT_PADI 0
+#define STATE_RECEIVED_PADO 1
+#define STATE_SENT_PADR 2
+#define STATE_SESSION 3
+#define STATE_TERMINATED 4
+
+/* Header size of a PPPoE packet */
+#define PPPOE_OVERHEAD 6 /* type, code, session, length */
+#define HDR_SIZE (sizeof(struct ethhdr) + PPPOE_OVERHEAD)
+#define MAX_PPPOE_PAYLOAD (ETH_DATA_LEN - PPPOE_OVERHEAD)
+#define MAX_PPPOE_MTU (MAX_PPPOE_PAYLOAD - 2)
+
+#define VENDOR_ADSL_FORUM 0xde9
+
+#define MAX_SID 65534
+#define SECRET_LENGTH 16
+#define COOKIE_LENGTH 24
+
+struct pppoe_tag_t
+{
+ struct list_head entry;
+ int type;
+ int len;
+};
+
+struct pppoe_packet_t
+{
+ uint8_t src[ETH_ALEN];
+ uint8_t dst[ETH_ALEN];
+ int code;
+ uint16_t sid;
+ struct list_head tags;
+};
+
+struct pppoe_serv_t
+{
+ struct list_head entry;
+ struct triton_context_t ctx;
+ struct triton_md_handler_t hnd;
+ uint8_t hwaddr[ETH_ALEN];
+ char *ifname;
+
+ uint8_t secret[SECRET_LENGTH];
+ DES_key_schedule des_ks;
+
+ pthread_mutex_t lock;
+ struct pppoe_conn_t *conn[MAX_SID];
+ uint16_t sid;
+ int stopping:1;
+
+ unsigned int conn_cnt;
+ struct list_head conn_list;
+ struct list_head pado_list;
+};
+
+extern int conf_verbose;
+extern char *conf_service_name;
+extern char *conf_ac_name;
+extern char *conf_pado_delay;
+
+extern unsigned int stat_active;
+extern unsigned int stat_delayed_pado;
+extern unsigned long stat_PADI_recv;
+extern unsigned long stat_PADO_sent;
+extern unsigned long stat_PADR_recv;
+extern unsigned long stat_PADR_dup_recv;
+extern unsigned long stat_PADS_sent;
+
+extern pthread_rwlock_t serv_lock;
+extern struct list_head serv_list;
+
+int mac_filter_check(const uint8_t *addr);
+void pppoe_server_start(const char *intf, void *client);
+void pppoe_server_stop(const char *intf);
+
+extern int pado_delay;
+void dpado_check_next(int conn_cnt);
+void dpado_check_prev(int conn_cnt);
+int dpado_parse(const char *str);
+
+struct rad_packet_t;
+int tr101_send_access_request(struct pppoe_tag *tr101, struct rad_packet_t *pack);
+int tr101_send_accounting_request(struct pppoe_tag *tr101, struct rad_packet_t *pack);
+
+#endif
+
diff --git a/accel-pppd/ctrl/pppoe/tr101.c b/accel-pppd/ctrl/pppoe/tr101.c
new file mode 100644
index 0000000..cfb0fbc
--- /dev/null
+++ b/accel-pppd/ctrl/pppoe/tr101.c
@@ -0,0 +1,97 @@
+#include <string.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <net/ethernet.h>
+
+#include "triton.h"
+#include "ppp.h"
+#include "log.h"
+#include "radius.h"
+#include "memdebug.h"
+
+#include "pppoe.h"
+
+#define OPT_CIRCUIT_ID 0x01
+#define OPT_REMOTE_AGENT_ID 0x02
+#define OPT_ACTUAL_DATA_RATE_UP 0x81
+#define OPT_ACTUAL_DATA_RATE_DOWN 0x82
+#define OPT_MIN_DATA_RATE_UP 0x83
+#define OPT_MAX_DATA_RATE_DOWN 0x84
+
+static int tr101_send_request(struct pppoe_tag *tr101, struct rad_packet_t *pack, int type)
+{
+ uint8_t *ptr = (uint8_t *)tr101->tag_data + 4;
+ uint8_t *endptr = (uint8_t *)tr101->tag_data + ntohs(tr101->tag_len);
+ int id, len;
+ char str[64];
+
+ while (ptr < endptr) {
+ if (ptr + 2 > endptr)
+ goto inval;
+ id = *ptr++;
+ len = *ptr++;
+ if (ptr + len - 2 > endptr)
+ goto inval;
+ if (type && id > 0x80)
+ continue;
+ switch (id) {
+ case OPT_CIRCUIT_ID:
+ if (len - 2 > 63)
+ goto inval;
+ memcpy(str, ptr, len);
+ str[len - 2] = 0;
+ if (rad_packet_add_str(pack, "ADSL-Forum", "ADSL-Agent-Circuit-Id", str))
+ return -1;
+ break;
+ case OPT_REMOTE_AGENT_ID:
+ if (len - 2 > 63)
+ goto inval;
+ memcpy(str, ptr, len);
+ str[len - 2] = 0;
+ if (rad_packet_add_str(pack, "ADSL-Forum", "ADSL-Agent-Remote-Id", str))
+ return -1;
+ break;
+ case OPT_ACTUAL_DATA_RATE_UP:
+ if (len != 6)
+ goto inval;
+ if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Data-Rate-Upstream", ntohl(*(uint32_t *)ptr)))
+ return -1;
+ break;
+ case OPT_ACTUAL_DATA_RATE_DOWN:
+ if (len != 6)
+ goto inval;
+ if (rad_packet_add_int(pack, "ADSL-Forum", "Actual-Data-Rate-Downstream", ntohl(*(uint32_t *)ptr)))
+ return -1;
+ break;
+ case OPT_MIN_DATA_RATE_UP:
+ if (len != 6)
+ goto inval;
+ if (rad_packet_add_int(pack, "ADSL-Forum", "Minimum-Data-Rate-Upstream", ntohl(*(uint32_t *)ptr)))
+ return -1;
+ break;
+ case OPT_MAX_DATA_RATE_DOWN:
+ if (len != 6)
+ goto inval;
+ if (rad_packet_add_int(pack, "ADSL-Forum", "Maximum-Data-Rate-Upstream", ntohl(*(uint32_t *)ptr)))
+ return -1;
+ break;
+ }
+ ptr += len - 2;
+ }
+
+ return 0;
+
+inval:
+ log_ppp_warn("pppoe:tr101: invalid tag received\n");
+ return -1;
+}
+
+int tr101_send_access_request(struct pppoe_tag *tr101, struct rad_packet_t *pack)
+{
+ return tr101_send_request(tr101, pack, 1);
+}
+
+int tr101_send_accounting_request(struct pppoe_tag *tr101, struct rad_packet_t *pack)
+{
+ return tr101_send_request(tr101, pack, 0);
+}