#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "crypto.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 "iputils.h" #include "connlimit.h" #include "vlan_mon.h" #include "pppoe.h" #include "memdebug.h" #define SID_MAX 65536 #ifndef min #define min(x,y) ((x)<(y)?(x):(y)) #endif struct pppoe_conn_t { struct list_head entry; struct triton_context_t ctx; struct pppoe_serv_t *serv; 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 - 4]; struct ap_ctrl 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; uint16_t ppp_max_payload; }; struct padi_t { struct list_head entry; struct timespec ts; uint8_t addr[ETH_ALEN]; }; struct iplink_arg { pcre *re; const char *opt; void *cli; long *arg1; }; int conf_verbose; char *conf_service_name[255]; int conf_accept_any_service; char *conf_ac_name; int conf_ifname_in_sid; char *conf_pado_delay; int conf_tr101 = 1; int conf_padi_limit = 0; int conf_mppe = MPPE_UNSET; int conf_sid_uppercase = 0; static const char *conf_ip_pool; static const char *conf_ipv6_pool; static const char *conf_dpv6_pool; static const char *conf_ifname; enum {CSID_MAC, CSID_IFNAME, CSID_IFNAME_MAC}; static int conf_called_sid; static int conf_cookie_timeout; static const char *conf_vlan_name; static int conf_vlan_timeout; static mempool_t conn_pool; static mempool_t pado_pool; static mempool_t padi_pool; unsigned int stat_starting; unsigned int stat_active; unsigned int stat_delayed_pado; unsigned long stat_PADI_recv; unsigned long stat_PADI_drop; unsigned long stat_PADO_sent; unsigned long stat_PADR_recv; unsigned long stat_PADR_dup_recv; unsigned long stat_PADS_sent; unsigned int total_padi_cnt; unsigned long stat_filtered; pthread_rwlock_t serv_lock = PTHREAD_RWLOCK_INITIALIZER; LIST_HEAD(serv_list); static int connlimit_loaded; static pthread_mutex_t sid_lock = PTHREAD_MUTEX_INITIALIZER; static unsigned long *sid_map; static unsigned long *sid_ptr; static int sid_idx; static uint8_t bc_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; static void pppoe_send_PADT(struct pppoe_conn_t *conn); void pppoe_server_free(struct pppoe_serv_t *serv); static int init_secret(struct pppoe_serv_t *serv); static void __pppoe_server_start(const char *ifname, const char *opt, void *cli, int parent_ifindex, int vid, int vlan_mon); static void pppoe_serv_timeout(struct triton_timer_t *t); static void set_vlan_timeout(struct pppoe_serv_t *serv); static void pppoe_serv_start_timer(struct pppoe_serv_t *serv) { pthread_mutex_lock(&serv->lock); if (serv->conn_cnt) { pthread_mutex_unlock(&serv->lock); return; } if (conf_vlan_timeout) { serv->timer.expire = pppoe_serv_timeout; serv->timer.expire_tv.tv_sec = conf_vlan_timeout; if (serv->timer.tpd) triton_timer_mod(&serv->timer, 0); else triton_timer_add(&serv->ctx, &serv->timer, 0); pthread_mutex_unlock(&serv->lock); } else { pthread_mutex_unlock(&serv->lock); pppoe_disc_stop(serv); pppoe_server_free(serv); } } static void disconnect(struct pppoe_conn_t *conn) { struct pppoe_serv_t *serv = conn->serv; if (conn->ppp_started) { dpado_check_prev(__sync_fetch_and_sub(&stat_active, 1)); conn->ppp_started = 0; ap_session_terminate(&conn->ppp.ses, TERM_USER_REQUEST, 1); } pppoe_send_PADT(conn); triton_event_fire(EV_CTRL_FINISHED, &conn->ppp.ses); log_ppp_info1("disconnected\n"); pthread_mutex_lock(&serv->lock); list_del(&conn->entry); serv->conn_cnt--; if (serv->conn_cnt == 0) { if (serv->stopping) { triton_context_call(&serv->ctx, (triton_event_func)pppoe_server_free, serv); pthread_mutex_unlock(&serv->lock); } else if (serv->vlan_mon) { triton_context_call(&serv->ctx, (triton_event_func)pppoe_serv_start_timer, serv); pthread_mutex_unlock(&conn->serv->lock); } else pthread_mutex_unlock(&serv->lock); } else pthread_mutex_unlock(&serv->lock); pthread_mutex_lock(&sid_lock); sid_map[conn->sid/(8*sizeof(long))] |= 1 << (conn->sid % (8*sizeof(long))); pthread_mutex_unlock(&sid_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); if (conn->tr101) _free(conn->tr101); triton_context_unregister(&conn->ctx); mempool_free(conn); } static void ppp_started(struct ap_session *ses) { log_ppp_debug("pppoe: ppp started\n"); } static void ppp_finished(struct ap_session *ses) { struct ppp_t *ppp = container_of(ses, typeof(*ppp), ses); 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) ap_session_terminate(&conn->ppp.ses, 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 void pppoe_conn_ctx_switch(struct triton_context_t *ctx, void *arg) { struct pppoe_conn_t *conn = arg; net = conn->ppp.ses.net; log_switch(ctx, &conn->ppp.ses); } 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, uint16_t ppp_max_payload) { struct pppoe_conn_t *conn; unsigned long *old_sid_ptr; conn = mempool_alloc(conn_pool); if (!conn) { log_error("pppoe: out of memory\n"); return NULL; } memset(conn, 0, sizeof(*conn)); pthread_mutex_lock(&sid_lock); old_sid_ptr = sid_ptr; while (1) { int bit = ffsl(*sid_ptr) - 1; if (bit != -1) { conn->sid = sid_idx*8*sizeof(long) + bit; *sid_ptr &= ~(1lu << bit); } if (++sid_idx == SID_MAX/8/sizeof(long)) { sid_ptr = sid_map; sid_idx = 0; } else sid_ptr++; if (bit != -1) break; if (sid_ptr == old_sid_ptr) break; } pthread_mutex_unlock(&sid_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 - 4); conn->ctx.before_switch = pppoe_conn_ctx_switch; conn->ctx.close = pppoe_conn_close; conn->ctrl.ctx = &conn->ctx; conn->ctrl.started = ppp_started; conn->ctrl.finished = ppp_finished; conn->ctrl.terminate = ppp_terminate; conn->ctrl.max_mtu = min(ETH_DATA_LEN, serv->mtu) - 8; conn->ctrl.type = CTRL_TYPE_PPPOE; conn->ctrl.ppp = 1; conn->ctrl.name = "pppoe"; conn->ctrl.ifname = serv->ifname; conn->ctrl.mppe = conf_mppe; if (ppp_max_payload > ETH_DATA_LEN - 8) conn->ctrl.max_mtu = min(ppp_max_payload, serv->mtu - 8); if (conf_called_sid == CSID_IFNAME) conn->ctrl.called_station_id = _strdup(serv->ifname); else if (conf_called_sid == CSID_IFNAME_MAC) { conn->ctrl.called_station_id = _malloc(IFNAMSIZ + 19); if (conf_sid_uppercase) 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, "%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 { conn->ctrl.called_station_id = _malloc(IFNAMSIZ + 19); if (conf_ifname_in_sid == 2 || conf_ifname_in_sid == 3) if (conf_sid_uppercase) 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, "%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 if (conf_sid_uppercase) 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]); 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]); } conn->ctrl.calling_station_id = _malloc(IFNAMSIZ + 19); if (conf_ifname_in_sid == 1 || conf_ifname_in_sid == 3) if (conf_sid_uppercase) 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, "%s:%02x:%02x:%02x:%02x:%02x:%02x", serv->ifname, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); else if (conf_sid_uppercase) sprintf(conn->ctrl.calling_station_id, "%02X:%02X:%02X:%02X:%02X:%02X", 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]); ppp_init(&conn->ppp); conn->ppp.ses.net = serv->net; conn->ppp.ses.ctrl = &conn->ctrl; conn->ppp.ses.chan_name = conn->ctrl.calling_station_id; if (conf_ip_pool) conn->ppp.ses.ipv4_pool_name = _strdup(conf_ip_pool); if (conf_ipv6_pool) conn->ppp.ses.ipv6_pool_name = _strdup(conf_ipv6_pool); if (conf_dpv6_pool) conn->ppp.ses.dpv6_pool_name = _strdup(conf_dpv6_pool); if (conf_ifname) conn->ppp.ses.ifname_rename = _strdup(conf_ifname); triton_context_register(&conn->ctx, conn); pthread_mutex_lock(&serv->lock); list_add_tail(&conn->entry, &serv->conn_list); if (serv->timer.tpd) triton_timer_del(&serv->timer); serv->conn_cnt++; pthread_mutex_unlock(&serv->lock); return conn; } static void connect_channel(struct pppoe_conn_t *conn) { int sock; struct sockaddr_pppox sp; triton_event_fire(EV_CTRL_STARTING, &conn->ppp.ses); triton_event_fire(EV_CTRL_STARTED, &conn->ppp.ses); sock = net->socket(AF_PPPOX, SOCK_DGRAM, PX_PROTO_OE); if (sock < 0) { log_error("pppoe: socket(PPPOX): %s\n", strerror(errno)); goto out_err; } fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC); 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 (net->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 && triton_module_loaded("radius")) { 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.ses, &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 - 4)) 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_tag_u16(struct pppoe_tag *tag) { log_info2("%i", (uint16_t)ntohs(*(uint16_t *)tag->tag_data)); } static void print_packet(const char *ifname, const char *op, 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("%s: %s [PPPoE ", ifname, op); 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); if (n + sizeof(*tag) > ntohs(hdr->length)) { log_info2(" ..."); break; } if (n + sizeof(*tag) + ntohs(tag->tag_len) > ntohs(hdr->length)) { log_info2(" ..."); break; } switch (ntohs(tag->tag_type)) { case TAG_END_OF_LIST: log_info2(" "); break; case TAG_SERVICE_NAME: log_info2(" "); break; case TAG_AC_NAME: log_info2(" "); break; case TAG_HOST_UNIQ: log_info2(" "); break; case TAG_AC_COOKIE: log_info2(" "); break; case TAG_VENDOR_SPECIFIC: if (ntohs(tag->tag_len) < 4) log_info2(" "); else log_info2(" ", ntohl(*(uint32_t *)tag->tag_data)); break; case TAG_RELAY_SESSION_ID: log_info2(" "); break; case TAG_PPP_MAX_PAYLOAD: log_info2(" "); break; case TAG_SERVICE_NAME_ERROR: log_info2(" "); break; case TAG_AC_SYSTEM_ERROR: log_info2(" "); break; case TAG_GENERIC_ERROR: log_info2(" "); break; default: log_info2(" ", ntohs(tag->tag_type)); break; } } log_info2("]\n"); } static void generate_cookie(struct pppoe_serv_t *serv, const uint8_t *src, uint8_t *cookie, const struct pppoe_tag *host_uniq, const struct pppoe_tag *relay_sid) { MD5_CTX ctx; DES_cblock key; DES_key_schedule ks; int i; union { DES_cblock b[3]; uint8_t raw[24]; } u1, u2; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); memcpy(key, serv->hwaddr, 6); key[6] = src[4]; key[7] = src[5]; 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); if (relay_sid) MD5_Update(&ctx, relay_sid->tag_data, ntohs(relay_sid->tag_len)); MD5_Final(u1.raw, &ctx); if (host_uniq) { uint8_t buf[16]; MD5_Init(&ctx); MD5_Update(&ctx, serv->secret, SECRET_LENGTH); MD5_Update(&ctx, host_uniq->tag_data, ntohs(host_uniq->tag_len)); MD5_Final(buf, &ctx); for (i = 0; i < 4; i++) u1.raw[16 + i] = buf[i] ^ buf[i + 4] ^ buf[i + 8] ^ buf[i + 12]; } else memset(u1.raw + 16, 0, 4); *(uint32_t *)(u1.raw + 20) = ts.tv_sec + conf_cookie_timeout; for (i = 0; i < 3; i++) DES_ecb_encrypt(&u1.b[i], &u2.b[i], &ks, DES_ENCRYPT); 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, const struct pppoe_tag *relay_sid) { MD5_CTX ctx; DES_cblock key; DES_key_schedule ks; int i; union { DES_cblock b[3]; uint8_t raw[24]; } u1, u2; struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); memcpy(key, serv->hwaddr, 6); key[6] = src[4]; key[7] = src[5]; DES_set_key(&key, &ks); 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); for (i = 0; i < 3; i++) DES_ecb_encrypt(&u2.b[i], &u1.b[i], &ks, DES_DECRYPT); if (*(uint32_t *)(u1.raw + 20) < ts.tv_sec) return 1; MD5_Init(&ctx); MD5_Update(&ctx, serv->secret, SECRET_LENGTH); MD5_Update(&ctx, serv->hwaddr, ETH_ALEN); MD5_Update(&ctx, src, ETH_ALEN); if (relay_sid) MD5_Update(&ctx, relay_sid->tag_data, ntohs(relay_sid->tag_len)); 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 void *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(struct pppoe_serv_t *serv, const uint8_t *pack) { struct sockaddr_ll addr = { .sll_family = AF_PACKET, .sll_protocol = htons(ETH_P_PPP_DISC), .sll_ifindex = serv->ifindex, .sll_halen = ETH_ALEN, }; struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); int len = ETH_HLEN + sizeof(*hdr) + ntohs(hdr->length); net->sendto(serv->disc_sock, pack, len, MSG_DONTWAIT, (struct sockaddr *)&addr, sizeof(addr)); } 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, uint16_t ppp_max_payload) { 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[0]) { int i = 0; do { add_tag(pack, TAG_SERVICE_NAME, (uint8_t *)conf_service_name[i], strlen(conf_service_name[i])); i++; } while(conf_service_name[i]); } if (service_name) add_tag2(pack, service_name); generate_cookie(serv, addr, cookie, host_uniq, relay_sid); 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 (ppp_max_payload) { ppp_max_payload = htons(ppp_max_payload); add_tag(pack, TAG_PPP_MAX_PAYLOAD, &ppp_max_payload, 2); } if (conf_verbose) print_packet(serv->ifname, "send", pack); __sync_add_and_fetch(&stat_PADO_sent, 1); pppoe_send(serv, 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) print_packet(serv->ifname, "send", pack); pppoe_send(serv, 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 (conn->ctrl.max_mtu > ETH_DATA_LEN - 8) { uint16_t ppp_max_payload = htons(conn->ctrl.max_mtu); add_tag(pack, TAG_PPP_MAX_PAYLOAD, &ppp_max_payload, 2); } if (conf_verbose) print_packet(conn->serv->ifname, "send", pack); __sync_add_and_fetch(&stat_PADS_sent, 1); pppoe_send(conn->serv, 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->relay_sid) add_tag2(pack, conn->relay_sid); if (conf_verbose) print_packet(conn->serv->ifname, "send", pack); pppoe_send(conn->serv, 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 (!ap_shutdown) pppoe_send_PADO(pado->serv, pado->addr, pado->host_uniq, pado->relay_sid, pado->service_name, pado->ppp_max_payload); free_delayed_pado(pado); } static int check_padi_limit(struct pppoe_serv_t *serv, uint8_t *addr) { struct padi_t *padi; struct timespec ts; if (serv->padi_limit == 0) goto connlimit_check; clock_gettime(CLOCK_MONOTONIC, &ts); while (!list_empty(&serv->padi_list)) { padi = list_entry(serv->padi_list.next, typeof(*padi), entry); if ((ts.tv_sec - padi->ts.tv_sec) * 1000 + (ts.tv_nsec - padi->ts.tv_nsec) / 1000000 > 1000) { list_del(&padi->entry); mempool_free(padi); serv->padi_cnt--; __sync_sub_and_fetch(&total_padi_cnt, 1); } else break; } if (serv->padi_cnt == serv->padi_limit) return -1; if (conf_padi_limit && total_padi_cnt >= conf_padi_limit) return -1; list_for_each_entry(padi, &serv->padi_list, entry) { if (memcmp(padi->addr, addr, ETH_ALEN) == 0) return -1; } padi = mempool_alloc(padi_pool); if (!padi) return -1; padi->ts = ts; memcpy(padi->addr, addr, ETH_ALEN); list_add_tail(&padi->entry, &serv->padi_list); serv->padi_cnt++; __sync_add_and_fetch(&total_padi_cnt, 1); connlimit_check: if (connlimit_loaded && connlimit_check(cl_key_from_mac(addr))) return -1; return 0; } 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 len, n, service_match = conf_service_name[0] == NULL; struct delayed_pado_t *pado; struct timespec ts; uint16_t ppp_max_payload = 0; __sync_add_and_fetch(&stat_PADI_recv, 1); if (ap_shutdown || pado_delay == -1) return; if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) return; if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) return; if (check_padi_limit(serv, ethhdr->h_source)) { __sync_add_and_fetch(&stat_PADI_drop, 1); if (conf_verbose) { clock_gettime(CLOCK_MONOTONIC, &ts); if (ts.tv_sec - 60 >= serv->last_padi_limit_warn) { log_warn("pppoe: discarding overlimit PADI packets on interface %s\n", serv->ifname); serv->last_padi_limit_warn = ts.tv_sec; } } return; } 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 (conf_service_name[0]) { int svc_index = 0; do { if (ntohs(tag->tag_len) == strlen(conf_service_name[svc_index]) && memcmp(tag->tag_data, conf_service_name[svc_index], ntohs(tag->tag_len)) == 0) { service_match = 1; break; } svc_index++; } while(conf_service_name[svc_index]); } else service_name_tag = tag; break; case TAG_HOST_UNIQ: host_uniq_tag = tag; break; case TAG_RELAY_SESSION_ID: relay_sid_tag = tag; break; case TAG_PPP_MAX_PAYLOAD: if (ntohs(tag->tag_len) == 2) ppp_max_payload = ntohs(*(uint16_t *)tag->tag_data); break; } } if (conf_verbose) print_packet(serv->ifname, "recv", pack); if (!service_match && !conf_accept_any_service) { if (conf_verbose) log_warn("pppoe: discarding PADI packet (Service-Name mismatch)\n"); return; } if (ppp_max_payload > serv->mtu - 8) ppp_max_payload = serv->mtu - 8; 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->ppp_max_payload = ppp_max_payload; pado->timer.expire = pado_timer; pado->timer.expire_tv.tv_sec = pado_delay / 1000; pado->timer.expire_tv.tv_usec = (pado_delay % 1000) * 1000; 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, ppp_max_payload); } 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; uint16_t ppp_max_payload = 0; __sync_add_and_fetch(&stat_PADR_recv, 1); if (ap_shutdown) return; if (conf_max_starting && ap_session_stat.starting >= conf_max_starting) return; if (conf_max_sessions && ap_session_stat.active + ap_session_stat.starting >= conf_max_sessions) 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) print_packet(serv->ifname, "recv", 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); if (n + sizeof(*tag) > ntohs(hdr->length)) { if (conf_verbose) log_warn("pppoe: discard PADR packet (truncated tag)\n"); return; } if (n + sizeof(*tag) + ntohs(tag->tag_len) > ntohs(hdr->length)) { if (conf_verbose) log_warn("pppoe: discard PADR packet (invalid tag length)\n"); return; } 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[0]) { int svc_index = 0; do { if (ntohs(tag->tag_len) == strlen(conf_service_name[svc_index]) && memcmp(tag->tag_data, conf_service_name[svc_index], ntohs(tag->tag_len)) == 0) { service_match = 1; break; } svc_index++; } while(conf_service_name[svc_index]); } 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) if (conf_tr101) tr101_tag = tag; case TAG_PPP_MAX_PAYLOAD: if (ntohs(tag->tag_len) == 2) ppp_max_payload = ntohs(*(uint16_t *)tag->tag_data); 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, relay_sid_tag)) { if (conf_verbose) log_warn("pppoe: discard PADR packet (incorrect AC-Cookie)\n"); return; } if (!service_match && !conf_accept_any_service) { 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.ses.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, ppp_max_payload); 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); triton_context_wakeup(&conn->ctx); } } 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; uint16_t sid = ntohs(hdr->sid); 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) print_packet(serv->ifname, "recv", pack); pthread_mutex_lock(&serv->lock); list_for_each_entry(conn, &serv->conn_list, entry) { if (conn->sid == sid) { if (!memcmp(conn->addr, ethhdr->h_source, ETH_ALEN)) triton_context_call(&conn->ctx, (void (*)(void *))disconnect, conn); break; } } pthread_mutex_unlock(&serv->lock); } void pppoe_serv_read(uint8_t *data) { struct pppoe_serv_t *serv = container_of(triton_context_self(), typeof(*serv), ctx); uint8_t *pack = data + 4; struct pppoe_hdr *hdr = (struct pppoe_hdr *)(pack + ETH_HLEN); int n = *(int *)data; 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; } mempool_free(data); } static void pppoe_serv_close(struct triton_context_t *ctx) { struct pppoe_serv_t *serv = container_of(ctx, typeof(*serv), ctx); if (serv->stopping) return; serv->stopping = 1; pppoe_disc_stop(serv); pthread_mutex_lock(&serv->lock); if (!serv->conn_cnt) { pthread_mutex_unlock(&serv->lock); pppoe_server_free(serv); return; } pthread_mutex_unlock(&serv->lock); } static void pppoe_serv_timeout(struct triton_timer_t *t) { struct pppoe_serv_t *serv = container_of(t, typeof(*serv), timer); pthread_mutex_lock(&serv->lock); if (serv->conn_cnt) { pthread_mutex_unlock(&serv->lock); return; } pthread_mutex_unlock(&serv->lock); pppoe_disc_stop(serv); pppoe_server_free(serv); } static int parse_server(const char *opt, int *padi_limit, struct ap_net **net) { char *ptr, *endptr; char name[64]; while (*opt == ',') { opt++; ptr = strchr(opt, '='); if (!ptr) goto out_err; if (!strncmp(opt, "padi-limit=", sizeof("padi-limit=") - 1)) { *padi_limit = strtol(ptr + 1, &endptr, 10); if (*endptr != 0 && *endptr != ',') goto out_err; opt = endptr; } else if (!strncmp(opt, "net=", sizeof("net=") - 1)) { ptr++; for (endptr = ptr + 1; *endptr && *endptr != ','; endptr++); if (endptr - ptr >= sizeof(name)) goto out_err; memcpy(name, ptr, endptr - ptr); name[endptr - ptr] = 0; *net = ap_net_find(name); if (!*net) goto out_err; } else goto out_err; } return 0; out_err: return -1; } static int __pppoe_add_interface_re(int index, int flags, const char *name, int iflink, int vid, struct iplink_arg *arg) { if (pcre_exec(arg->re, NULL, name, strlen(name), 0, 0, NULL, 0) < 0) return 0; __pppoe_server_start(name, arg->opt, arg->cli, iflink, vid, 0); return 0; } static void pppoe_add_interface_re(const char *opt, void *cli) { pcre *re = NULL; const char *pcre_err; char *pattern; const char *ptr; int pcre_offset; struct iplink_arg arg; for (ptr = opt; *ptr && *ptr != ','; ptr++); pattern = _malloc(ptr - (opt + 3) + 1); memcpy(pattern, opt + 3, ptr - (opt + 3)); pattern[ptr - (opt + 3)] = 0; re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); if (!re) { log_error("pppoe: %s at %i\r\n", pcre_err, pcre_offset); return; } arg.re = re; arg.opt = ptr; arg.cli = cli; iplink_list((iplink_list_func)__pppoe_add_interface_re, &arg); pcre_free(re); _free(pattern); } void pppoe_server_start(const char *opt, void *cli) { char name[IFNAMSIZ]; const char *ptr; if (strlen(opt) > 3 && memcmp(opt, "re:", 3) == 0) { pppoe_add_interface_re(opt, cli); return; } ptr = strchr(opt, ','); if (ptr) { memcpy(name, opt, ptr - opt); name[ptr - opt] = 0; __pppoe_server_start(name, ptr, cli, -1, 0, 0); } else __pppoe_server_start(opt, opt, cli, -1, 0, 0); } static void pppoe_serv_ctx_switch(struct triton_context_t *ctx, void *arg) { struct pppoe_serv_t *serv = arg; net = serv->net; log_switch(ctx, NULL); } static void __pppoe_server_start(const char *ifname, const char *opt, void *cli, int parent_ifindex, int vid, int vlan_mon) { struct pppoe_serv_t *serv; struct ifreq ifr; int padi_limit = conf_padi_limit; struct ap_net *net = def_net; if (parse_server(opt, &padi_limit, &net)) { if (cli) cli_sendv(cli, "failed to parse '%s'\r\n", opt); else log_error("pppoe: failed to parse '%s'\r\n", opt); return; } pthread_rwlock_rdlock(&serv_lock); list_for_each_entry(serv, &serv_list, entry) { if (serv->net == net && !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); if (vid && !vlan_mon && vlan_mon_check_busy(parent_ifindex, vid)) return; 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; } strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (net->sock_ioctl(SIOCGIFFLAGS, &ifr)) { if (cli) cli_sendv(cli, "%s: %s\r\n", ifname, strerror(errno)); log_error("pppoe: %s: %s\n", ifname, strerror(errno)); goto out_err; } if (!(ifr.ifr_flags & IFF_UP)) { ifr.ifr_flags |= IFF_UP; net->sock_ioctl(SIOCSIFFLAGS, &ifr); } if (net->sock_ioctl(SIOCGIFHWADDR, &ifr)) { if (cli) cli_sendv(cli, "ioctl(SIOCGIFHWADDR): %s\r\n", strerror(errno)); log_error("pppoe: ioctl(SIOCGIFHWADDR): %s\n", strerror(errno)); goto out_err; } #ifdef ARPHDR_ETHER if (ifr.ifr_hwaddr.sa_family != ARPHDR_ETHER) { log_error("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_error("pppoe: interface %s has not unicast address\n", ifname); goto out_err; } memcpy(serv->hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); if (net->sock_ioctl(SIOCGIFMTU, &ifr)) { if (cli) cli_sendv(cli, "ioctl(SIOCGIFMTU): %s\r\n", strerror(errno)); log_error("pppoe: ioctl(SIOCGIFMTU): %s\n", strerror(errno)); goto out_err; } serv->mtu = ifr.ifr_mtu; if (net->sock_ioctl(SIOCGIFINDEX, &ifr)) { if (cli) cli_sendv(cli, "ioctl(SIOCGIFINDEX): %s\r\n", strerror(errno)); log_error("pppoe: ioctl(SIOCGIFINDEX): %s\n", strerror(errno)); goto out_err; } if (parent_ifindex == -1 && net == def_net) vid = iplink_vlan_get_vid(ifr.ifr_ifindex, &parent_ifindex); serv->ctx.close = pppoe_serv_close; serv->ctx.before_switch = pppoe_serv_ctx_switch; serv->ifname = _strdup(ifname); serv->ifindex = ifr.ifr_ifindex; serv->parent_ifindex = parent_ifindex; serv->vid = vid; serv->net = net; pthread_mutex_init(&serv->lock, NULL); INIT_LIST_HEAD(&serv->conn_list); INIT_LIST_HEAD(&serv->pado_list); INIT_LIST_HEAD(&serv->padi_list); serv->padi_limit = padi_limit; triton_context_register(&serv->ctx, serv); serv->disc_sock = pppoe_disc_start(serv); if (serv->disc_sock < 0) { log_error("pppoe: %s: failed to create discovery socket\n", ifname); triton_context_unregister(&serv->ctx); goto out_err; } if (vlan_mon) { serv->vlan_mon = 1; set_vlan_timeout(serv); } pthread_rwlock_wrlock(&serv_lock); list_add_tail(&serv->entry, &serv_list); pthread_rwlock_unlock(&serv_lock); triton_context_wakeup(&serv->ctx); return; out_err: _free(serv); } static void _conn_stop(struct pppoe_conn_t *conn) { ap_session_terminate(&conn->ppp.ses, TERM_ADMIN_RESET, 0); } void _server_stop(struct pppoe_serv_t *serv) { struct pppoe_conn_t *conn; if (serv->stopping) return; serv->stopping = 1; pppoe_disc_stop(serv); 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); } if (serv->timer.tpd) triton_timer_del(&serv->timer); if (serv->vlan_mon) { log_info2("pppoe: remove vlan %s\n", serv->ifname); iplink_vlan_del(serv->ifindex); vlan_mon_add_vid(serv->parent_ifindex, ETH_P_PPP_DISC, serv->vid); } 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); } void __export pppoe_get_stat(unsigned int **starting, unsigned int **active) { *starting = &stat_starting; *active = &stat_active; } static int init_secret(struct pppoe_serv_t *serv) { DES_cblock key; if (read(urandom_fd, serv->secret, SECRET_LENGTH) < 0) { log_error("pppoe: failed to read /dev/urandom: %s\n", strerror(errno)); return -1; } memset(key, 0, sizeof(key)); DES_random_key(&key); DES_set_key(&key, &serv->des_ks); return 0; } static void set_vlan_timeout(struct pppoe_serv_t *serv) { if (conf_vlan_timeout) { serv->timer.expire = pppoe_serv_timeout; serv->timer.expire_tv.tv_sec = conf_vlan_timeout; if (!serv->conn_cnt) triton_timer_add(&serv->ctx, &serv->timer, 0); } } void pppoe_vlan_mon_notify(int ifindex, int vid, int vlan_ifindex) { struct conf_sect_t *sect = conf_get_section("pppoe"); struct conf_option_t *opt; struct ifreq ifr; char *ptr; int len, r, svid; pcre *re = NULL; const char *pcre_err; char *pattern; int pcre_offset; char ifname[IFNAMSIZ]; if (!sect) return; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_ifindex = ifindex; if (ioctl(sock_fd, SIOCGIFNAME, &ifr, sizeof(ifr))) { log_error("pppoe: vlan-mon: failed to get interface name, ifindex=%i\n", ifindex); return; } svid = iplink_vlan_get_vid(ifindex, NULL); #ifdef USE_LUA if (!memcmp(conf_vlan_name, "lua:", 4)) r = ipoe_lua_make_vlan_name(conf_vlan_name + 4, ifr.ifr_name, svid, vid, ifname); else #endif r = make_vlan_name(conf_vlan_name, ifr.ifr_name, svid, vid, ifname); if (r) { log_error("pppoe: vlan-mon: %s.%i: interface name is too long\n", ifr.ifr_name, vid); return; } if (vlan_ifindex) { struct pppoe_serv_t *serv; pthread_rwlock_rdlock(&serv_lock); list_for_each_entry(serv, &serv_list, entry) { if (serv->ifindex == vlan_ifindex) { if (!serv->vlan_mon) { serv->vlan_mon = 1; set_vlan_timeout(serv); } pthread_rwlock_unlock(&serv_lock); return; } } pthread_rwlock_unlock(&serv_lock); log_info2("pppoe: create vlan %s parent %s\n", ifname, ifr.ifr_name); ifr.ifr_ifindex = vlan_ifindex; if (ioctl(sock_fd, SIOCGIFNAME, &ifr, sizeof(ifr))) { log_error("pppoe: vlan-mon: failed to get interface name, ifindex=%i\n", ifindex); return; } if (ioctl(sock_fd, SIOCGIFFLAGS, &ifr, sizeof(ifr))) return; if (ifr.ifr_flags & IFF_UP) { ifr.ifr_flags &= ~IFF_UP; if (ioctl(sock_fd, SIOCSIFFLAGS, &ifr, sizeof(ifr))) return; } if (strcmp(ifr.ifr_name, ifname)) { strcpy(ifr.ifr_newname, ifname); if (ioctl(sock_fd, SIOCSIFNAME, &ifr, sizeof(ifr))) { log_error("pppoe: vlan-mon: failed to rename interface %s to %s\n", ifr.ifr_name, ifr.ifr_newname); return; } strcpy(ifr.ifr_name, ifname); } } else { log_info2("pppoe: create vlan %s parent %s\n", ifname, ifr.ifr_name); if (iplink_vlan_add(ifname, ifindex, vid)) return; } len = strlen(ifname); memcpy(ifr.ifr_name, ifname, len + 1); if (ioctl(sock_fd, SIOCGIFINDEX, &ifr, sizeof(ifr))) { log_error("pppoe: vlan-mon: %s: failed to get interface index\n", ifr.ifr_name); return; } list_for_each_entry(opt, §->items, entry) { if (strcmp(opt->name, "interface")) continue; if (!opt->val) continue; ptr = strchr(opt->val, ','); if (!ptr) ptr = strchr(opt->val, 0); if (ptr - opt->val > 3 && memcmp(opt->val, "re:", 3) == 0) { pattern = _malloc(ptr - (opt->val + 3) + 1); memcpy(pattern, opt->val + 3, ptr - (opt->val + 3)); pattern[ptr - (opt->val + 3)] = 0; re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); _free(pattern); if (!re) continue; r = pcre_exec(re, NULL, ifr.ifr_name, len, 0, 0, NULL, 0); pcre_free(re); if (r < 0) continue; __pppoe_server_start(ifr.ifr_name, opt->val, NULL, ifindex, vid, 1); return; } else if (ptr - opt->val == len && memcmp(opt->val, ifr.ifr_name, len) == 0) { __pppoe_server_start(ifr.ifr_name, opt->val, NULL, ifindex, vid, 1); return; } } log_warn("pppoe: vlan %s not started\n", ifname); iplink_vlan_del(ifr.ifr_ifindex); vlan_mon_del_vid(ifindex, ETH_P_PPP_DISC, vid); } static void add_vlan_mon(const char *opt, long *mask) { const char *ptr; struct ifreq ifr; int ifindex; long mask1[4096/8/sizeof(long)]; struct pppoe_serv_t *serv; for (ptr = opt; *ptr && *ptr != ','; ptr++); if (ptr - opt >= IFNAMSIZ) { log_error("pppoe: vlan-mon=%s: interface name is too long\n", opt); return; } memset(&ifr, 0, sizeof(ifr)); memcpy(ifr.ifr_name, opt, ptr - opt); ifr.ifr_name[ptr - opt] = 0; if (ioctl(sock_fd, SIOCGIFINDEX, &ifr)) { log_error("pppoe: '%s': ioctl(SIOCGIFINDEX): %s\n", ifr.ifr_name, strerror(errno)); return; } ifindex = ifr.ifr_ifindex; ioctl(sock_fd, SIOCGIFFLAGS, &ifr); if (!(ifr.ifr_flags & IFF_UP)) { ifr.ifr_flags |= IFF_UP; ioctl(sock_fd, SIOCSIFFLAGS, &ifr); } memcpy(mask1, mask, sizeof(mask1)); list_for_each_entry(serv, &serv_list, entry) { if (serv->parent_ifindex == ifindex && !(mask1[serv->vid / (8*sizeof(long))] & 1lu << (serv->vid % (8*sizeof(long))))) { mask1[serv->vid / (8*sizeof(long))] |= 1lu << (serv->vid % (8*sizeof(long))); if (!serv->vlan_mon) { serv->vlan_mon = 1; set_vlan_timeout(serv); } } } vlan_mon_add(ifindex, ETH_P_PPP_DISC, mask1, sizeof(mask1)); } static int __load_vlan_mon_re(int index, int flags, const char *name, int iflink, int vid, struct iplink_arg *arg) { struct ifreq ifr; long mask1[4096/8/sizeof(long)]; struct pppoe_serv_t *serv; if (pcre_exec(arg->re, NULL, name, strlen(name), 0, 0, NULL, 0) < 0) return 0; memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, name); ioctl(sock_fd, SIOCGIFFLAGS, &ifr); if (!(ifr.ifr_flags & IFF_UP)) { ifr.ifr_flags |= IFF_UP; ioctl(sock_fd, SIOCSIFFLAGS, &ifr); } memcpy(mask1, arg->arg1, sizeof(mask1)); list_for_each_entry(serv, &serv_list, entry) { if (serv->parent_ifindex == index && !(mask1[serv->vid / (8*sizeof(long))] & (1lu << (serv->vid % (8*sizeof(long)))))) { mask1[serv->vid / (8*sizeof(long))] |= 1lu << (serv->vid % (8*sizeof(long))); if (!serv->vlan_mon) { serv->vlan_mon = 1; set_vlan_timeout(serv); } } } vlan_mon_add(index, ETH_P_PPP_DISC, mask1, sizeof(mask1)); return 0; } static void load_vlan_mon_re(const char *opt, long *mask, int len) { pcre *re = NULL; const char *pcre_err; char *pattern; const char *ptr; int pcre_offset; struct iplink_arg arg; for (ptr = opt; *ptr && *ptr != ','; ptr++); pattern = _malloc(ptr - (opt + 3) + 1); memcpy(pattern, opt + 3, ptr - (opt + 3)); pattern[ptr - (opt + 3)] = 0; re = pcre_compile2(pattern, 0, NULL, &pcre_err, &pcre_offset, NULL); if (!re) { log_error("pppoe: '%s': %s at %i\r\n", pattern, pcre_err, pcre_offset); return; } arg.re = re; arg.opt = opt; arg.arg1 = mask; iplink_list((iplink_list_func)__load_vlan_mon_re, &arg); pcre_free(re); _free(pattern); } static void load_vlan_mon(struct conf_sect_t *sect) { struct conf_option_t *opt; long mask[4096/8/sizeof(long)]; static int registered = 0; if (!registered) { vlan_mon_register_proto(ETH_P_PPP_DISC, pppoe_vlan_mon_notify); registered = 1; } vlan_mon_del(-1, ETH_P_PPP_DISC); list_for_each_entry(opt, §->items, entry) { if (strcmp(opt->name, "vlan-mon")) continue; if (!opt->val) continue; if (parse_vlan_mon(opt->val, mask)) continue; if (strlen(opt->val) > 3 && !memcmp(opt->val, "re:", 3)) load_vlan_mon_re(opt->val, mask, sizeof(mask)); else add_vlan_mon(opt->val, mask); } } static void load_config(void) { char *opt; struct conf_sect_t *s = conf_get_section("pppoe"); opt = conf_get_opt("pppoe", "verbose"); if (opt) conf_verbose = atoi(opt); opt = conf_get_opt("pppoe", "accept-any-service"); if (opt) conf_accept_any_service = atoi(opt); opt = conf_get_opt("pppoe", "ac-name"); if (!opt) opt = conf_get_opt("pppoe", "AC-Name"); if (opt) { if (conf_ac_name) _free(conf_ac_name); conf_ac_name = _strdup(opt); } else conf_ac_name = _strdup("accel-ppp"); opt = conf_get_opt("pppoe", "service-name"); if (!opt) opt = conf_get_opt("pppoe", "Service-Name"); if (opt) { if (conf_service_name[0]) { int i = 0; do { _free(conf_service_name[i]); i++; } while(conf_service_name[i]); conf_service_name[0] = NULL; } char *conf_service_name_string = _strdup(opt); char *p = strtok (conf_service_name_string, ","); int i = 0; while (p != NULL && i<255) { conf_service_name[i++] = _strdup(p); p = strtok(NULL, ","); } conf_service_name[i] = NULL; _free(conf_service_name_string); } opt = conf_get_opt("pppoe", "ifname-in-sid"); if (opt) { if (!strcmp(opt, "calling-sid")) conf_ifname_in_sid = 1; else if (!strcmp(opt, "called-sid")) conf_ifname_in_sid = 2; else if (!strcmp(opt, "both")) conf_ifname_in_sid = 3; else if (atoi(opt) >= 0) conf_ifname_in_sid = atoi(opt); } opt = conf_get_opt("pppoe", "pado-delay"); if (!opt) opt = conf_get_opt("pppoe", "PADO-Delay"); if (opt) dpado_parse(opt); opt = conf_get_opt("pppoe", "tr101"); if (opt) conf_tr101 = atoi(opt); opt = conf_get_opt("pppoe", "padi-limit"); if (opt) conf_padi_limit = atoi(opt); opt = conf_get_opt("pppoe", "sid-uppercase"); if (opt) conf_sid_uppercase = atoi(opt); opt = conf_get_opt("pppoe", "cookie-timeout"); if (opt) conf_cookie_timeout = atoi(opt); else conf_cookie_timeout = 5; conf_mppe = MPPE_UNSET; opt = conf_get_opt("pppoe", "mppe"); if (opt) { if (strcmp(opt, "deny") == 0) conf_mppe = MPPE_DENY; else if (strcmp(opt, "allow") == 0) conf_mppe = MPPE_ALLOW; else if (strcmp(opt, "prefer") == 0) conf_mppe = MPPE_PREFER; else if (strcmp(opt, "require") == 0) conf_mppe = MPPE_REQUIRE; } conf_ip_pool = conf_get_opt("pppoe", "ip-pool"); conf_ipv6_pool = conf_get_opt("pppoe", "ipv6-pool"); conf_dpv6_pool = conf_get_opt("pppoe", "ipv6-pool-delegate"); conf_ifname = conf_get_opt("pppoe", "ifname"); conf_called_sid = CSID_MAC; opt = conf_get_opt("pppoe", "called-sid"); if (opt) { if (!strcmp(opt, "mac")) conf_called_sid = CSID_MAC; else if (!strcmp(opt, "ifname")) conf_called_sid = CSID_IFNAME; else if (!strcmp(opt, "ifname:mac")) conf_called_sid = CSID_IFNAME_MAC; else log_error("pppoe: unknown called-sid type\n"); } opt = conf_get_opt("pppoe", "vlan-name"); if (opt) conf_vlan_name = opt; else conf_vlan_name = "%I.%N"; opt = conf_get_opt("pppoe", "vlan-timeout"); if (opt && atoi(opt) > 0) conf_vlan_timeout = atoi(opt); else conf_vlan_timeout = 60; load_vlan_mon(s); } static void load_interfaces() { struct conf_sect_t *s = conf_get_section("pppoe"); struct conf_option_t *opt; list_for_each_entry(opt, &s->items, entry) { if (!strcmp(opt->name, "interface")) { if (opt->val) pppoe_server_start(opt->val, NULL); } } } static void pppoe_init(void) { int fd; uint8_t *ptr; ptr = malloc(SID_MAX/8); memset(ptr, 0xff, SID_MAX/8); ptr[0] = 0xfe; ptr[SID_MAX/8-1] = 0x7f; sid_ptr = sid_map = (unsigned long *)ptr; fd = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_OE); if (fd >= 0) close(fd); else if (system("modprobe -q pppoe")) log_warn("failed to load pppoe kernel module\n"); conn_pool = mempool_create(sizeof(struct pppoe_conn_t)); pado_pool = mempool_create(sizeof(struct delayed_pado_t)); padi_pool = mempool_create(sizeof(struct padi_t)); conf_service_name[0] = NULL; if (!conf_get_section("pppoe")) { log_error("pppoe: no configuration, disabled...\n"); return; } load_interfaces(); load_config(); connlimit_loaded = triton_module_loaded("connlimit"); triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config); } DEFINE_INIT(21, pppoe_init);