/* * Copyright 2007 Benjamin C. R. LaHaise, All Rights Reserved. * Permission is hereby granted to copy under the terms of the GPLv2 * or later. See the file LICENSE for details. */ #include "config.h" #include "babd.h" #include "ctrlfd.h" #include "bpppoe.h" #include "aps_if.h" #include "bvirt_ns.h" #include #include #include #include /* from the packet man page so that we compile under libc5 or glibc */ #include #include #include /* for the glibc version number */ #if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1 #include #include /* the L2 protocols */ #else #include #include #include #include /* The L2 protocols */ #endif static pppoed *pppoed_list; static pppoed *pppoed_list_last; pppoe_session *global_sessions[65536]; //#define USERLAND_SESSIONS 1 int pppoe_session::HardOutput(CPppPacket *pkt, int pri) { if (m_channel != &ch || !parent) return CLink::HardOutput(pkt, pri); pkt->Push16(pkt->GetLength() /* length */); pkt->Push16(session_id); pkt->Push8(0x00 /* code */); pkt->Push8(0x11 /* ver type */); pkt->Push16(ETH_P_PPPoE_SESN /* ether type */); pkt->Push8(parent->our_addr[5]); pkt->Push8(parent->our_addr[4]); pkt->Push8(parent->our_addr[3]); pkt->Push8(parent->our_addr[2]); pkt->Push8(parent->our_addr[1]); pkt->Push8(parent->our_addr[0]); pkt->Push8(peer_mac[5]); pkt->Push8(peer_mac[4]); pkt->Push8(peer_mac[3]); pkt->Push8(peer_mac[2]); pkt->Push8(peer_mac[1]); pkt->Push8(peer_mac[0]); int len = pkt->GetLength(); int ret = sendto(parent->pppoed_disc_fd, pkt->m_start, len, 0, &parent->sa, sizeof parent->sa); if (len != ret) perror("pppoe_session::HardOutput: write(pppoed_disc_fd)"); return ret; } void pppoed::send_padt(u16 session_id, u8 *our_addr, u8 *peer_addr) { /* make a PADT */ CPppPacket padt; padt.Push16(padt.GetLength() /* length */); padt.Push16(session_id); padt.Push8(PPPoE_CODE_PADT /* code */); padt.Push8(0x11 /* ver type */); padt.Push16(ETH_P_PPPoE_SESN /* ether type */); padt.Push8(our_addr[5]); padt.Push8(our_addr[4]); padt.Push8(our_addr[3]); padt.Push8(our_addr[2]); padt.Push8(our_addr[1]); padt.Push8(our_addr[0]); padt.Push8(peer_addr[5]); padt.Push8(peer_addr[4]); padt.Push8(peer_addr[3]); padt.Push8(peer_addr[2]); padt.Push8(peer_addr[1]); padt.Push8(peer_addr[0]); int len = padt.GetLength(); int ret = sendto(this->pppoed_disc_fd, padt.m_start, len, 0, &sa, sizeof sa); if (len != ret) perror("pppoe_session::HardOutput: write(pppoed_disc_fd)"); /* sent PADT */ } void pppoe_session::HardHangup(void) { Stop(); if (m_channel != &ch) { if (parent) { parent->remove_session(this, session_id); parent = NULL; } CLink::HardHangup(); delete this; return; } ch.state = CS_DISCONNECTING; if (parent) parent->send_padt(session_id, parent->our_addr, peer_mac); else fprintf(stderr, "FIXME: send_padt for outgoing call\n"); ConnectComplete(0x400); delete this; } void pppoe_session::pppoe_input(u8 *buf, int len) { struct pppoehdr *h = (struct pppoehdr *)buf; buf += sizeof *h; len -= sizeof *h; if (h->src_addr[0] != peer_mac[0] || h->src_addr[1] != peer_mac[1] || h->src_addr[2] != peer_mac[2] || h->src_addr[3] != peer_mac[3] || h->src_addr[4] != peer_mac[4] || h->src_addr[5] != peer_mac[5]) { if (h->code != PPPoE_CODE_PADT) parent->send_padt(ntohs(h->sesn_id), h->dst_addr, h->src_addr); return; } if (h->ether_type == htons(ETH_P_PPPoE_SESN) && h->code == PPPoE_CODE_DATA) { CLink *link = this; u16 h_len = ntohs(h->length); if (h_len < len) len = h_len; link->Input(buf, len); return; } if (h->code == PPPoE_CODE_PADT) { if (m_channel != &ch) { if (parent) { parent->remove_session(this, session_id); parent = NULL; } CLink::HardHangup(); delete this; return; } ch.state = CS_DISCONNECTING; ConnectComplete(0x400); delete this; } } pppoed::pppoed(const char *dev_name, int disc_fd, char *addr, bvirt_ns_t *netns, int relay_mode) { m_next = NULL; m_prev = NULL; if (pppoed_list_last) { pppoed_list_last->m_next = this; m_prev = pppoed_list_last; m_prevp = &m_prev->m_next; pppoed_list_last = this; } else { pppoed_list = this; pppoed_list_last = this; m_prevp = &pppoed_list; } ac_name = strdup("Babylon"); ac_name_len = strlen(ac_name); m_netns = netns; m_relay_mode = relay_mode; m_relay = NULL; memset(&sa, 0, sizeof sa); sa.sa_family = 0; strncpy(pppoed_dev_name, dev_name, sizeof pppoed_dev_name); strcpy(sa.sa_data, pppoed_dev_name); pppoed_disc_fd = disc_fd; pppoed_sesn_fd = -1; memcpy(our_addr, addr, sizeof our_addr); last_session_id = 0; memset(&sessions, 0, sizeof sessions); SelectAddEvent(disc_fd, SEL_READ); } pppoed::~pppoed() { for (unsigned i=0; i<65536; i++) if (sessions[i]) delete sessions[i]; if (pppoed_disc_fd != -1) { SelectRemoveEvent(pppoed_disc_fd, SEL_READ); close(pppoed_disc_fd); } if (pppoed_sesn_fd != -1) { SelectRemoveEvent(pppoed_sesn_fd, SEL_READ); close(pppoed_sesn_fd); } if (pppoed_list_last == this) pppoed_list_last = m_prev; *m_prevp = m_next; if (m_next) { m_next->m_prevp = m_prevp; m_next->m_prev = m_prev; } free((void *)ac_name); } static unsigned char *find_tag(struct pppoehdr *h, u16 wanted_tag) { unsigned char *tag = (unsigned char *)(h + 1); int len = ntohs(h->length); while (len >= 4) { u16 cur_tag = ntohs(*(u16 *)tag); u16 cur_len = ntohs(((u16 *)tag)[1]); if (cur_len > len) break; if (cur_tag == wanted_tag) return tag; cur_len += 4; tag += cur_len; len -= cur_len; } return NULL; } void pppoed::send_pado(struct pppoehdr *h) { #define mktag(type, tagdata, taglen) \ do { \ *(u16 *)buf = htons(type); \ buf += 2; \ *(u16 *)buf = htons(taglen); \ buf += 2; \ memcpy(buf, tagdata, taglen); \ buf += taglen; \ } while (0) unsigned char tmp[1500]; unsigned char *buf = tmp; struct pppoehdr *reply = (struct pppoehdr *)buf; unsigned char *host_uniq = find_tag(h, PPPoE_TAG_HOST_UNIQ); unsigned char *relay_sesn_id = find_tag(h, PPPoE_TAG_RELAY_SESSION_ID); buf += sizeof(*reply); memcpy(reply->dst_addr, h->src_addr, 6); memcpy(reply->src_addr, our_addr, 6); reply->ether_type = htons(ETH_P_PPPoE_DISC); reply->vertype = 0x11; reply->code = PPPoE_CODE_PADO; reply->sesn_id = 0x0000; reply->length = 0; mktag(PPPoE_TAG_AC_NAME, ac_name, ac_name_len); mktag(PPPoE_TAG_SERVICE_NAME, "internet", 8); if (host_uniq) { u16 len = ntohs(((u16 *)host_uniq)[1]); mktag(PPPoE_TAG_HOST_UNIQ, host_uniq+4, len); } if (relay_sesn_id) { u16 len = ntohs(((u16 *)relay_sesn_id)[1]); mktag(PPPoE_TAG_RELAY_SESSION_ID, relay_sesn_id+4, len); } reply->length = htons((buf - tmp) - sizeof(*reply)); if ((buf - tmp) != sendto(pppoed_disc_fd, tmp, buf - tmp, 0, &sa, sizeof sa)) perror("write(pppoed_disc_fd)"); } void pppoed::send_padi(pppoed *relay_src, struct pppoehdr *h) { unsigned char tmp[1500]; unsigned char *buf = tmp; struct pppoehdr *reply = (struct pppoehdr *)buf; unsigned char *host_uniq = find_tag(h, PPPoE_TAG_HOST_UNIQ); unsigned char *relay_sesn_id = find_tag(h, PPPoE_TAG_RELAY_SESSION_ID); unsigned char *service_name_tag = find_tag(h, PPPoE_TAG_SERVICE_NAME); unsigned char *ac_name_tag = find_tag(h, PPPoE_TAG_AC_NAME); buf += sizeof(*reply); memcpy(reply->dst_addr, "\xff\xff\xff\xff\xff\xff", 6); memcpy(reply->src_addr, our_addr, 6); reply->ether_type = htons(ETH_P_PPPoE_DISC); reply->vertype = 0x11; reply->code = PPPoE_CODE_PADI; reply->sesn_id = 0x0000; reply->length = 0; if (ac_name_tag) { u16 len = ntohs(((u16 *)ac_name_tag)[1]); mktag(PPPoE_TAG_AC_NAME, ac_name_tag + 4, len); } const char *service_name = "internet"; u16 service_name_len = 8; if (service_name_tag) { service_name = (char *)service_name_tag + 4; service_name_len = ntohs(((u16 *)service_name_tag)[1]); } mktag(PPPoE_TAG_SERVICE_NAME, service_name, service_name_len); if (host_uniq) { u16 len = ntohs(((u16 *)host_uniq)[1]); mktag(PPPoE_TAG_HOST_UNIQ, host_uniq+4, len); } if (relay_sesn_id) { u16 len = ntohs(((u16 *)relay_sesn_id)[1]); mktag(PPPoE_TAG_RELAY_SESSION_ID, relay_sesn_id+4, len); relay_padi(relay_src, h, relay_sesn_id + 4, len); } else if (relay_src) { u8 tag[48]; u16 len; len = snprintf((char *)tag, 48, "%s%%%02x%02x%02x%02x%02x%02x%%%02x%02x%02x%02x%02x%02x", relay_src->pppoed_dev_name, relay_src->our_addr[0], relay_src->our_addr[1], relay_src->our_addr[2], relay_src->our_addr[3], relay_src->our_addr[4], relay_src->our_addr[5], h->src_addr[0], h->src_addr[1], h->src_addr[2], h->src_addr[3], h->src_addr[4], h->src_addr[5] ); mktag(PPPoE_TAG_RELAY_SESSION_ID, tag, len); relay_padi(relay_src, h, tag, len); } reply->length = htons((buf - tmp) - sizeof(*reply)); if ((buf - tmp) != sendto(pppoed_disc_fd, tmp, buf - tmp, 0, &sa, sizeof sa)) perror("write(pppoed_disc_fd)"); } relay_session_t *pppoed::lookup_relay_session_by_tag(u8 *tag, u16 tag_len) { relay_session_t *sesn; for (sesn = m_relay_session_list; sesn; sesn = sesn->m_next) { if (sesn->m_tag_len != tag_len) continue; if (!memcmp(sesn->m_tag, tag, tag_len)) return sesn; } return NULL; } relay_session_t *pppoed::lookup_relay_session_by_client_mac(u8 *mac) { relay_session_t *sesn; for (sesn = m_relay_session_list; sesn; sesn = sesn->m_next) { if (!memcmp(sesn->m_client_mac, mac, 6)) return sesn; } return NULL; } void pppoed::relay_padi(pppoed *src, struct pppoehdr *h, u8 *tag, u16 tag_len) { relay_session_t *sesn = lookup_relay_session_by_tag(tag, tag_len); if (sesn) { if (sesn->m_pppoed != src) fprintf(stderr, "session daemon mismatch\n"); return; } sesn = new relay_session_t; sesn->m_relay = this; sesn->m_pppoed = src; sesn->m_next = m_relay_session_list; m_relay_session_list = sesn; sesn->m_tag = (u8 *)malloc(tag_len + 1); memcpy(sesn->m_tag, tag, tag_len); sesn->m_tag[tag_len] = 0; sesn->m_tag_len = tag_len; memcpy(sesn->m_client_mac, h->src_addr, 6); sesn->m_state = "PADI"; } void pppoed::relay_pado(relay_session_t *relay_sesn, struct pppoehdr *h) { unsigned char tmp[1500]; unsigned char *buf = tmp; struct pppoehdr *reply = (struct pppoehdr *)buf; static u16 copy_tags[] = { PPPoE_TAG_SERVICE_NAME, PPPoE_TAG_AC_NAME, PPPoE_TAG_HOST_UNIQ, PPPoE_TAG_AC_COOKIE, PPPoE_TAG_RELAY_SESSION_ID, PPPoE_TAG_SERVICE_NAME_ERROR, PPPoE_TAG_AC_SYSTEM_ERROR, PPPoE_TAG_GENERIC_ERROR }; buf += sizeof(*reply); memcpy(reply->dst_addr, relay_sesn->m_client_mac, 6); memcpy(reply->src_addr, our_addr, 6); reply->ether_type = htons(ETH_P_PPPoE_DISC); reply->vertype = 0x11; reply->code = h->code; reply->sesn_id = h->sesn_id; reply->length = 0; for (unsigned i = 0; i < sizeof(copy_tags) / 2; i++) { u8 *tag = find_tag(h, copy_tags[i]); if (tag) { u16 len = ntohs(((u16 *)tag)[1]); mktag(copy_tags[i], tag + 4, len); } } reply->length = htons((buf - tmp) - sizeof(*reply)); if ((buf - tmp) != sendto(pppoed_disc_fd, tmp, buf - tmp, 0, &sa, sizeof sa)) perror("write(pppoed_disc_fd)"); } void pppoed::relay_padr(relay_session_t *relay_sesn, struct pppoehdr *h) { unsigned char tmp[1500]; unsigned char *buf = tmp; struct pppoehdr *reply = (struct pppoehdr *)buf; static u16 copy_tags[] = { PPPoE_TAG_SERVICE_NAME, PPPoE_TAG_AC_NAME, PPPoE_TAG_HOST_UNIQ, PPPoE_TAG_AC_COOKIE, PPPoE_TAG_RELAY_SESSION_ID, PPPoE_TAG_SERVICE_NAME_ERROR, PPPoE_TAG_AC_SYSTEM_ERROR, PPPoE_TAG_GENERIC_ERROR }; buf += sizeof(*reply); memcpy(reply->dst_addr, relay_sesn->m_server_mac, 6); memcpy(reply->src_addr, our_addr, 6); reply->ether_type = htons(ETH_P_PPPoE_DISC); reply->vertype = 0x11; reply->code = PPPoE_CODE_PADR; reply->sesn_id = 0x0000; reply->length = 0; int found_relay_tag = 0; for (unsigned i = 0; i < sizeof(copy_tags) / 2; i++) { u8 *tag = find_tag(h, copy_tags[i]); if (tag) { u16 len = ntohs(((u16 *)tag)[1]); mktag(copy_tags[i], tag + 4, len); if (copy_tags[i] == PPPoE_TAG_RELAY_SESSION_ID) found_relay_tag = 1; } } if (!found_relay_tag) mktag(PPPoE_TAG_RELAY_SESSION_ID, relay_sesn->m_tag, relay_sesn->m_tag_len); reply->length = htons((buf - tmp) - sizeof(*reply)); if ((buf - tmp) != sendto(pppoed_disc_fd, tmp, buf - tmp, 0, &sa, sizeof sa)) perror("write(pppoed_disc_fd)"); } pppoe_session::pppoe_session(u8 *mac, u16 id, pppoed *pppoed) { if (mac) memcpy(peer_mac, mac, sizeof peer_mac); else memset(peer_mac, 0, sizeof peer_mac); session_id = id; parent = pppoed; memset(&ch, 0, sizeof ch); char tmp[16]; sprintf(tmp, "pppoe%u", session_id); ch.device_id = ~0U; ch.device_name = strdup(tmp); ch.dev_class = "pppoe"; ch.file_name = "/dev/bpppoe"; ch.link = this; ch.link->m_hard_mtu = 1492; ch.link->m_hard_mru = 1492; m_channel = &ch; } pppoe_session::pppoe_session(channel_t *ch) { memset(peer_mac, 0, sizeof(peer_mac)); CLink *link = this; link->m_channel = ch; parent = NULL; session_id = 0; } pppoe_session::~pppoe_session() { Stop(); if (parent) { parent->remove_session(this, session_id); parent = NULL; } remove_disc_chans(); remove_multihop_link(); if (m_channel == &ch) { free((void *)ch.device_name); ch.device_name = NULL; } else { /* created via addchan() */ free((void *)m_channel->dev_class); free((void *)m_channel->device_name); free((void *)m_channel->file_name); delete m_channel; m_channel = NULL; } } pppoe_session *find_idle_session(u8 *mac_addr, u16 id, pppoed *pppoed) { #if USERLAND_SESSIONS return new pppoe_session(mac_addr, id, pppoed); #else int retry = 1; again: /* FIXME. This is a linear search. */ for (CLink *link = linkListHead; link != NULL; link = link->m_next) { if (link->m_channel && link->m_channel->state == CS_IDLE && !strcmp(link->m_channel->dev_class, "pppoe")) { pppoe_session *session = (pppoe_session *)link; char number[64]; sprintf(number, "%02x%02x%02x%02x%02x%02x%04x%s", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5], id, pppoed->pppoed_dev_name); if (session->reopen(0)) { fprintf(stderr, "reopen failed\n"); continue; } strcpy(((CLink *)session)->ifOptions.phone, number); int ret = session->ch_ioctl(BIOCDIAL, (long)(void *)number); if (-1 == ret) { perror("BIOCDIAL failed"); Log(LF_ERROR|LF_CALL, "ioctl(%s, BIOCDIAL): %s", session->m_channel->device_name, strerror(errno)); continue; } memcpy(session->peer_mac, mac_addr, sizeof(session->peer_mac)); session->session_id = id; session->parent = pppoed; return session; } } if (retry) { retry = 0; addchan("/dev/bpppoe", "/dev/bpppoe", NULL, 0); goto again; } return NULL; #endif } u16 pppoed::alloc_sesn_id(unsigned char *mac_addr) { int i; for (i=0; i<65535; i++) { last_session_id ++; if (!last_session_id) last_session_id++; if (!global_sessions[last_session_id]) { pppoe_session *session; session = find_idle_session(mac_addr, last_session_id, this); if (!session) return 0; sessions[last_session_id] = session; global_sessions[last_session_id] = session; return last_session_id; } } return 0; } void pppoed::send_pads(struct pppoehdr *h) { unsigned char tmp[1500]; unsigned char *buf = tmp; struct pppoehdr *reply = (struct pppoehdr *)buf; unsigned char *host_uniq = find_tag(h, PPPoE_TAG_HOST_UNIQ); unsigned char *relay_sesn_id = find_tag(h, PPPoE_TAG_RELAY_SESSION_ID); u16 sesn_id; buf += sizeof(*reply); memcpy(reply->dst_addr, h->src_addr, 6); memcpy(reply->src_addr, our_addr, 6); reply->ether_type = htons(ETH_P_PPPoE_DISC); reply->vertype = 0x11; reply->code = PPPoE_CODE_PADS; sesn_id = alloc_sesn_id(h->src_addr); reply->sesn_id = htons(sesn_id); reply->length = 0; mktag(PPPoE_TAG_AC_NAME, ac_name, ac_name_len); mktag(PPPoE_TAG_SERVICE_NAME, "internet", 8); if (host_uniq) { u16 len = ntohs(((u16 *)host_uniq)[1]); mktag(PPPoE_TAG_HOST_UNIQ, host_uniq+4, len); } if (relay_sesn_id) { u16 len = ntohs(((u16 *)relay_sesn_id)[1]); mktag(PPPoE_TAG_RELAY_SESSION_ID, relay_sesn_id+4, len); } reply->length = htons((buf - tmp) - sizeof(*reply)); if ((buf - tmp) != sendto(pppoed_disc_fd, tmp, buf - tmp, 0, &sa, sizeof sa)) perror("write(pppoed_disc_fd)"); pppoe_session *session = sessions[sesn_id]; if (!session) { fprintf(stderr, "send_pads: no session dev %s session 0x%04x\n", pppoed_dev_name, sesn_id); send_padt(ntohs(h->sesn_id), h->dst_addr, h->src_addr); return; } fprintf(stderr, "PPPoE session up: dev %s session 0x%04x channel %s\n", pppoed_dev_name, sesn_id, session->get_name()); session->Open(); session->m_channel->state = CS_CONNECTED; session->Up(); sprintf(session->ifOptions.phone, "%02x%02x%02x%02x%02x%02x%04x%s", session->peer_mac[0], session->peer_mac[1], session->peer_mac[2], session->peer_mac[3], session->peer_mac[4], session->peer_mac[5], session->session_id, this->pppoed_dev_name); } void pppoed::SelectEvent(int fd, SelectEventType event) { if (!(event & SEL_READ)) return; unsigned char buf[4096]; int len = read(fd, buf, sizeof buf); if (len < 0) return; struct pppoehdr *h = (struct pppoehdr *)buf; if (h->vertype != 0x11) { fprintf(stderr, "pppoe: wrong version 0x%02x\n", h->vertype); return; } #if 0 fprintf(stderr, "from: %02x:%02x:%02x:%02x:%02x:%02x " "to: %02x:%02x:%02x:%02x:%02x:%02x\n", h->src_addr[0], h->src_addr[1], h->src_addr[2], h->src_addr[3], h->src_addr[4], h->src_addr[5], h->dst_addr[0], h->dst_addr[1], h->dst_addr[2], h->dst_addr[3], h->dst_addr[4], h->dst_addr[5]); fprintf(stderr, "sesn(0x%04x) pppoe length = %d (read = %d)\n", ntohs(h->sesn_id), ntohs(h->length), len); #endif switch(h->code) { case PPPoE_CODE_PADT: fprintf(stderr, "got PADT from %s %02x:%02x:%02x:%02x:%02x:%02x\n", pppoed_dev_name, h->src_addr[0], h->src_addr[1], h->src_addr[2], h->src_addr[3], h->src_addr[4], h->src_addr[5]); case 0: if (h->sesn_id) { pppoe_session *session = sessions[ntohs(h->sesn_id)]; if (session) { session->pppoe_input(buf, len); return; } if (h->code != PPPoE_CODE_PADT) send_padt(ntohs(h->sesn_id), h->dst_addr, h->src_addr); } break; case PPPoE_CODE_PADI: fprintf(stderr, "got PADI from %s %02x:%02x:%02x:%02x:%02x:%02x\n", pppoed_dev_name, h->src_addr[0], h->src_addr[1], h->src_addr[2], h->src_addr[3], h->src_addr[4], h->src_addr[5]); if (h->sesn_id != 0) { fprintf(stderr, "dropping PADI\n"); return; } /* Don't do anything with a PADI if we're in relay mode */ if (m_relay_mode) return; if (m_relay) m_relay->send_padi(this, h); else send_pado(h); break; case PPPoE_CODE_PADS: case PPPoE_CODE_PADO: { fprintf(stderr, "got %s from %s %02x:%02x:%02x:%02x:%02x:%02x\n", h->code == PPPoE_CODE_PADS ? "PADS" : "PADO", pppoed_dev_name, h->src_addr[0], h->src_addr[1], h->src_addr[2], h->src_addr[3], h->src_addr[4], h->src_addr[5]); u8 *tag = find_tag(h, PPPoE_TAG_RELAY_SESSION_ID); if (!tag) { fprintf(stderr, "missing relay session id tag\n"); break; } u16 tag_len = ntohs(((u16 *)tag)[1]); relay_session_t *relay_sesn = lookup_relay_session_by_tag(tag + 4, tag_len); if (!relay_sesn) { fprintf(stderr, "lookup of relay session tag failed\n"); break; } relay_sesn->update_access_time(); if (memcmp(relay_sesn->m_server_mac, "\xff\xff\xff\xff\xff\xff", 6) && memcmp(relay_sesn->m_server_mac, h->src_addr, 6)) { fprintf(stderr, "lookup of relay session found different mac\n"); break; } memcpy(relay_sesn->m_server_mac, h->src_addr, 6); if (h->code == PPPoE_CODE_PADO) relay_sesn->m_state = "PADO"; else if (h->code == PPPoE_CODE_PADS) relay_sesn->m_state = "PADS"; if ((h->code == PPPoE_CODE_PADS) && (h->sesn_id != 0)) { pppoe_session *sesn_srv, *sesn_cli; sesn_srv = find_idle_session(relay_sesn->m_server_mac, ntohs(h->sesn_id), this); sesn_srv->m_channel->state = CS_CONNECTED; sesn_srv->m_phase = PHASE_NETWORK; sessions[ntohs(h->sesn_id)] = sesn_srv; global_sessions[ntohs(h->sesn_id)] = sesn_srv; u16 cli_sesn_id = relay_sesn->m_pppoed->alloc_sesn_id(relay_sesn->m_client_mac); sesn_cli = relay_sesn->m_pppoed->sessions[cli_sesn_id]; sesn_cli->m_channel->state = CS_CONNECTED; sesn_cli->m_phase = PHASE_NETWORK; if (!sesn_srv || !sesn_cli) { fprintf(stderr, "FIXME: unable to get session\n"); *(char *)0 = 0; } h->sesn_id = htons(cli_sesn_id); int callid = sesn_srv->ch_ioctl(BIOCGETCALLID, 0); if (callid < 0) perror("BIOCGETCALLID"); sesn_cli->ch_ioctl(BIOC_SET_MULTIHOP, callid); ((CLink *)sesn_srv)->multihop_link = sesn_cli; ((CLink *)sesn_cli)->multihop_link = sesn_srv; sesn_srv->SetTimeConnected(); sesn_cli->SetTimeConnected(); sesn_srv->Start(BPPPOE_MULTIHOP_IDLE_INTERVAL); sesn_cli->Start(BPPPOE_MULTIHOP_IDLE_INTERVAL); } relay_sesn->m_pppoed->relay_pado(relay_sesn, h); break; } case PPPoE_CODE_PADR: fprintf(stderr, "got PADR from %s %02x:%02x:%02x:%02x:%02x:%02x\n", pppoed_dev_name, h->src_addr[0], h->src_addr[1], h->src_addr[2], h->src_addr[3], h->src_addr[4], h->src_addr[5]); if (h->dst_addr[0] == 0xff && h->dst_addr[1] == 0xff && h->dst_addr[2] == 0xff && h->dst_addr[3] == 0xff && h->dst_addr[4] == 0xff && h->dst_addr[5] == 0xff) { fprintf(stderr, "dropping PADR sent to broadcast\n"); return; } if (m_relay) { relay_session_t *sesn; sesn = m_relay->lookup_relay_session_by_client_mac(h->src_addr); if (!sesn) { fprintf(stderr, "dropping PADR sent to unknown relay session\n"); break; } sesn->update_access_time(); sesn->m_state = "PADR"; m_relay->relay_padr(sesn, h); } else send_pads(h); break; } return; } void pppoed::setup_sesn_sk(int sk) { pppoed_sesn_fd = sk; SelectAddEvent(sk, SEL_READ); } void pppoed::remove_session(pppoe_session *session, u16 idx) { if (sessions[idx] != session) *(char *)0 = 0; sessions[idx] = NULL; global_sessions[idx] = NULL; } pppoed *find_pppoed(bvirt_ns_t *netns, char *devname) { for (pppoed *pppoed = pppoed_list; pppoed; pppoed = pppoed->m_next) { if (pppoed->m_netns == netns && !strcmp(devname, pppoed->pppoed_dev_name)) return pppoed; } return NULL; } static int __bpppoe_listen(ctrlfd_t *cfd, char *devname, u16 type, char *our_addr, bvirt_ns_t **netns, int check) { int sk; cfd->printf("listen '%s'\n", devname); if (bvirt_parse_ns(cfd, &devname, netns)) return -1; if (check && find_pppoed(*netns, devname)) { cfd->printf("already listening on device %s%s%s\n", *netns ? (*netns)->m_name : "", *netns ? "^" : "", devname); cfd->done(-2); return -2; } sk = netns_socket(*netns, PF_INET, SOCK_PACKET, type); if (sk < 0) { cfd->perror("socket"); cfd->done(-1); return -1; } struct ifreq ifreq; memset(&ifreq, 0, sizeof(ifreq)); strncpy(ifreq.ifr_name, devname, sizeof(ifreq.ifr_name)); if (ioctl(sk, SIOCGIFHWADDR, &ifreq) < 0) { cfd->perror("SIOCGIFHWADDR:"); cfd->done(-1); close(sk); return -1; } if (our_addr) memcpy(our_addr, &ifreq.ifr_ifru.ifru_hwaddr.sa_data, 6); struct sockaddr sa; sa.sa_family = 0; strncpy(sa.sa_data, devname, sizeof(sa.sa_data)); if (bind(sk, &sa, sizeof(sa)) < 0) { cfd->perror("bind"); cfd->done(-1); close(sk); return -1; } return sk; } static int bpppoe_dev_listen(ctrlfd_t *cfd, char **devnamep, bvirt_ns_t **netnsp, char *our_addr) { char *_devname = strchr(*devnamep, '^'); int sk = __bpppoe_listen(cfd, *devnamep, htons(ETH_P_PPPoE_DISC), our_addr, netnsp, 1); if (sk < 0) return sk; if (_devname) { _devname++; *devnamep = _devname; } return sk; } static void bpppoe_listen(ctrlfd_t *cfd, char *devname, const char *ac_name, char *relay_dev, int relay_mode) { char our_addr[6]; bvirt_ns_t *netns = NULL; char *_devname = devname; int sk = bpppoe_dev_listen(cfd, &_devname, &netns, our_addr); if (sk < 0) return; /* get relay socket */ char relay_addr[6]; bvirt_ns_t *relay_netns = NULL; char *_relay_dev = relay_dev; int relay_sk = -1; pppoed *relay_daemon = NULL; if (relay_dev) { if (bvirt_parse_ns(cfd, &_relay_dev, &relay_netns)) return; relay_daemon = find_pppoed(relay_netns, _relay_dev); if (relay_daemon) { cfd->printf("found existing relay daemon\n"); if (!relay_daemon->m_relay_mode) { cfd->printf("relay target %s not in relay mode\n", _relay_dev); cfd->done(-2); return; } } else if ((relay_sk = bpppoe_dev_listen(cfd, &_relay_dev, &relay_netns, relay_addr)) < 0) { cfd->printf("bpppoe: unable to open relay socket '%s'\n", relay_dev); cfd->done(-2); close(sk); return; } else { _relay_dev = strdup(_relay_dev); relay_daemon = new pppoed(_relay_dev, relay_sk, relay_addr, relay_netns, 1); #ifdef USERLAND_SESSIONS int sesn_sk = __bpppoe_listen(cfd, _relay_dev, htons(ETH_P_PPPoE_SESN), NULL, &relay_netns, 0); if (sesn_sk < 0) { delete relay_daemon; if (_relay_dev) free(_relay_dev); return; } relay_daemon->setup_sesn_sk(sesn_sk); #endif } } pppoed *daemon = new pppoed(_devname, sk, our_addr, netns, relay_mode); if (ac_name) daemon->set_ac_name(ac_name); #ifdef USERLAND_SESSIONS int sesn_sk = __bpppoe_listen(cfd, devname, htons(ETH_P_PPPoE_SESN), NULL, &netns, 0); if (sesn_sk < 0) { delete daemon; if (_relay_dev) free(_relay_dev); return; } daemon->setup_sesn_sk(sesn_sk); #endif if (relay_daemon) daemon->setup_relay(relay_daemon); cfd->printf("okay(%p)\n", daemon); cfd->done(0); } void pppoed::setup_relay(pppoed *relay) { m_relay = relay; } void bpppoe_del(ctrlfd_t *cfd, char *str) { char *dev = str; bvirt_ns_t *netns = NULL; if (bvirt_parse_ns(cfd, &str, &netns)) return; pppoed *daemon = find_pppoed(netns, str); if (!daemon) { cfd->printf("Couldn't find pppoe instance '%s'\n", dev); cfd->done(-2); return; } if (daemon->m_relay_mode) { int found = 0; pppoed *relay; for (relay = pppoed_list; relay; relay = relay->m_next) { if (relay->m_relay == daemon) { cfd->printf("%s still in use as relay target for %s%s%s\n", str, relay->m_netns ? relay->m_netns->m_name : "", relay->m_netns ? "^" : "", relay->pppoed_dev_name); found = 1; } } if (found) { cfd->printf("not deleting\n"); cfd->done(-2); return; } } delete daemon; cfd->done(0); } void bpppoe_show_relay(ctrlfd_t *cfd) { cfd->printf("%-5s %-12s %-5s %-12s %-5s %4s %s\n", "dev", "client mac", "dev", "server mac", "state", "idle", "tag"); for (pppoed *daemon = pppoed_list; daemon; daemon = daemon->m_next) { for (relay_session_t *session = daemon->m_relay_session_list; session; session = session->m_next) { cfd->printf("%-5s %02x%02x%02x%02x%02x%02x " "%-5s %02x%02x%02x%02x%02x%02x %-5s %3lds %s\n", session->m_pppoed->pppoed_dev_name, session->m_client_mac[0], session->m_client_mac[1], session->m_client_mac[2], session->m_client_mac[3], session->m_client_mac[4], session->m_client_mac[5], daemon->pppoed_dev_name, session->m_server_mac[0], session->m_server_mac[1], session->m_server_mac[2], session->m_server_mac[3], session->m_server_mac[4], session->m_server_mac[5], session->m_state, session->get_idle_time(), session->m_tag ); } } cfd->done(0); } void do_bpppoe(ctrlfd_t *cfd, char *str) { if (!strncmp("listen ", str, 7)) { char *ac_name = NULL; str += 7; if (!strncmp("--ac-name=", str, 10)) { int i; str += 10; for (i=0; str[i] && str[i] != ' '; i++) ; if (str[i] != ' ') goto usage; str[i] = 0; ac_name = str; str += i + 1; } int relay_mode = 0; if (!strncmp(str, "relay ", 6)) { relay_mode = 1; str += 6; } char *dev = str; str = strchr(str, ' '); char *relay_dev = NULL; if (str) { if (relay_mode) { cfd->printf("relay can't relay\n"); cfd->done(-2); return; } *str++ = 0; if (strncmp(str, "relay ", 6)) { cfd->printf("expected 'relay '\n"); cfd->done(-2); return; } str += 6; relay_dev = str; } return bpppoe_listen(cfd, dev, ac_name, relay_dev, relay_mode); } else if (!strncmp("del ", str, 4)) return bpppoe_del(cfd, str + 4); else if (!strcmp("show-relay", str)) return bpppoe_show_relay(cfd); usage: cfd->printf("usage:\n"); cfd->printf(" bpppoe listen --ac-name=foo \n"); cfd->printf(" bpppoe listen --ac-name=foo relay \n"); cfd->printf(" bpppoe del \n"); cfd->printf(" bpppoe show-relay\n"); cfd->done(-2); } void pppoed::show_running_config(ctrlfd_t *cfd, int verbose) { cfd->printf("bpppoe listen --ac-name=%s %s%s%s%s%s%s%s%s\n", ac_name, m_relay_mode ? "relay " : "", m_netns ? m_netns->m_name : "", m_netns ? "^" : "", pppoed_dev_name, m_relay ? " relay " : "", m_relay ? (m_relay->m_netns ? m_relay->m_netns->m_name : "") : "", m_relay ? (m_relay->m_netns ? "^" : "") : "", m_relay ? m_relay->pppoed_dev_name : "" ); } void show_pppoed_list(ctrlfd_t *cfd, int verbose) { pppoed *pppoed; for (pppoed = pppoed_list; pppoed; pppoed = pppoed->m_next) pppoed->show_running_config(cfd, verbose); if (pppoed_list) cfd->my_putchar('\n'); } void relay_session_t::TimerExpired(void) { relay_session_t **m_prevp = &m_relay->m_relay_session_list; while (*m_prevp != this) m_prevp = &(*m_prevp)->m_next; *m_prevp = m_next; delete this; } void pppoe_session::TimerExpired(void) { unsigned long idle_ms = 0; ch_ioctl(BIOC_GET_IDLE_MS, (unsigned long)&idle_ms); if (idle_ms >= 60*60*1000UL) Hangup(); else Start(BPPPOE_MULTIHOP_IDLE_INTERVAL); }