diff options
Diffstat (limited to 'accel-pppd/ctrl/pppoe/pppoe.c')
-rw-r--r-- | accel-pppd/ctrl/pppoe/pppoe.c | 1288 |
1 files changed, 1288 insertions, 0 deletions
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"); +} + |