#ifdef USE_L2TP #include "l2tpd.h" #include "l2tp_tunnel.h" #include #include #include #include #include #include #include #include #include #include "l2tp_linux.h" #include "ctrlfd.h" #include "babd.h" static l2tpd_t *l2tpd_head, **l2tpd_tailp = &l2tpd_head; l2tpd_t::l2tpd_t(ctrlfd_t *cfd, struct sockaddr_in sin) { first_peer = NULL; nr_multihop = 0; memset(l2tp_tunnels, 0, sizeof(l2tp_tunnels)); memcpy(&our_sin, &sin, sizeof(our_sin)); udp_fd = socket(sin.sin_family, SOCK_DGRAM, IPPROTO_UDP); if (udp_fd < 0) { cfd->perror("socket(l2tpd_t: udp)"); cfd->done(-2); return; // FIXME leak } int one = 1; if (setsockopt(udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { cfd->perror("l2tpd_t:setsockopt(udp_fd, SO_REUSEADDR)"); cfd->done(-2); return; // FIXME leak } if (bind(udp_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { cfd->perror("l2tpd_t:bind(udp_fd)"); cfd->done(-2); return; // FIXME leak } l2tp_fd = socket(AF_L2TP, SOCK_DGRAM, 0); if (l2tp_fd < 0) { cfd->perror("socket(l2tpd_t: l2tp)"); cfd->done(-2); return; // FIXME leak } #if 0 struct sockaddr_l2tp l2tp_sa; l2tp_sa.sl_family = AF_L2TP; l2tp_sa.sl_sfd = udp_fd; l2tp_sa.sl_tunnel = htons(0); l2tp_sa.sl_session = htons(0); if (bind(l2tp_fd, (struct sockaddr *)&l2tp_sa, sizeof(l2tp_sa))) { cfd->perror("bind(l2tpd_t: l2tp)"); return; } #endif this->SelectSetEvents(udp_fd, SEL_READ); cfd->printf("l2tpd started\n"); cfd->done(0); next = NULL; prevp = l2tpd_tailp; *l2tpd_tailp = this; l2tpd_tailp = &this->next; } l2tpd_t::~l2tpd_t(void) { *prevp = this->next; next->prevp = prevp; next = NULL; prevp = NULL; } void l2tpd_t::handle_packet(char *buf, unsigned size, struct sockaddr_in *sin) { if (debug) fprintf(stderr, "l2tpd_t::handle_packet\n"); l2tp_peer_t *peer = find_make_peer(sin); peer->handle_packet(buf, size, sin); } l2tp_peer_t *l2tpd_t::find_peer(struct sockaddr_in *sin) { l2tp_peer_t *peer = first_peer; while (peer && (sin->sin_addr.s_addr != peer->remote_sin.sin_addr.s_addr || sin->sin_port != peer->remote_sin.sin_port)) peer = peer->next; return peer; } l2tp_peer_t *l2tpd_t::find_make_peer(struct sockaddr_in *sin, char *secret) { l2tp_peer_t *peer = find_peer(sin); if (!peer) { peer = new l2tp_peer_t(this, &our_sin, sin, secret); peer->next = first_peer; first_peer = peer; } return peer; } l2tp_tunnel_t *l2tpd_t::make_tunnel(struct sockaddr_in *sin, u16 tunnel) { l2tp_peer_t *peer = find_make_peer(sin); return peer->make_tunnel(); } void PacketHandler_t::SelectEvent(int fd, SelectEventType event) { struct sockaddr_in sin; char buf[2048]; unsigned i; //fprintf(stderr, "PacketHandler_t::SelectEvent\n"); for (i=0; i<100; i++) { int ret; socklen_t addrlen = sizeof(sin); ret = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT | MSG_TRUNC, (struct sockaddr *)&sin, &addrlen); if (ret < 0 && errno == EAGAIN) { #if 0 is_udp = 0; ret = recv(l2tp_fd, buf, sizeof(buf), MSG_DONTWAIT | MSG_TRUNC); if (ret < 0 && errno == EAGAIN) #endif break; } if (ret < 0) perror("l2tpd_t::SelectEvent: recvfrom()"); if (ret <= 0) break; if (ret > (int)sizeof(buf)) { fprintf(stderr, "l2tpd_t::SelectEvent: packet too large (%d)", ret); continue; } handle_packet(buf, ret, addrlen ? &sin : NULL); } } void l2tpd_t::dump_sessions(ctrlfd_t *cfd, int verbose) { for (l2tp_peer_t *peer = first_peer; peer; peer = peer->next) peer->dump_sessions(cfd, verbose); if (!verbose) cfd->done(0); } void l2tpd_t::dump_num_sessions(ctrlfd_t *cfd) { cfd->printf("number of Established sessions: %u\n", num_est_sessions); cfd->printf("total allocated sessions: %u\n", num_sessions); cfd->done(0); } void l2tpd_t::start_tunnel(ctrlfd_t *cfd, const char *ip) { struct sockaddr_in sin; l2tp_tunnel_t *tunnel; sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(strtoip(ip)); sin.sin_port = htons(1701); cfd->printf("creating tunnel\n"); tunnel = make_tunnel(&sin, 0); cfd->printf("local tunnel %d\n", tunnel->tunnel_id); tunnel->open_session(cfd); } void l2tpd_t::start_session(ctrlfd_t *cfd, char *ip) { struct sockaddr_in sin; l2tp_peer_t *peer; l2tp_tunnel_t *tunnel; const char *local; u32 local_ip = ~0U; l2tpd_t *daemon = this; char *site = NULL; while (*ip && isspace(*ip)) ip++; if (*ip && !isdigit(*ip)) { site = ip; while (*ip && !isspace(*ip)) ip++; if (*ip) *ip++ = '\0'; } sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(strtoip(ip)); sin.sin_port = htons(1701); local = strchr(ip, ' '); if (local) local_ip = htonl(strtoip(local)); if (local_ip != (u32)~0UL) { for (daemon = l2tpd_head; daemon; daemon = daemon->next) if (daemon->our_sin.sin_addr.s_addr == local_ip) break; if (!daemon) { cfd->printf("unable to find local ip '%s'\n", local); cfd->done(1); return; } } peer = daemon->find_peer(&sin); if (!peer) { peer = daemon->find_make_peer(&sin); cfd->printf("created new peer\n"); } else cfd->printf("found existing peer\n"); tunnel = peer->find_avail_tunnel(); cfd->printf("opening new session on tunnel %d for '%s'\n", tunnel->tunnel_id, site ? site : "l2tp"); tunnel->open_session(cfd, site); } static l2tpd_t *the_l2tpd = NULL; int l2tp_setup(FILE *cfg) { char buf[256]; int ret; while (fgets(buf, sizeof(buf), cfg)) { unsigned i = 0; while (isspace(buf[i])) i++; if ('#' == buf[i]) continue; /* skip comment */ char *tmp = strrchr(buf+i, '\n'); if (tmp) *tmp = 0; if (!buf[i]) continue; /* blank line */ ret = do_l2tpctl(new ctrlfd_t(2, 1), buf+i); if (ret) break; } return ret; } int add_l2tp(ctrlfd_t *cfd, char *str) { struct sockaddr_in sin; unsigned long ip = strtoip(str); if (ip == (unsigned long)-1L) { cfd->printf("invalid ip address '%s'\n", str); cfd->done(-2); return 2; } sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(ip); sin.sin_port = htons(1701); the_l2tpd = new l2tpd_t(cfd, sin); #if 0 // FIXME if (!the_l2tpd->okay) { cfd->printf("'add %s': error setting up l2tp\n", str); cfd->done(-2); return 2; } #endif return 0; } int do_l2tpctl(ctrlfd_t *cfd, char *str) { while (isspace(*str)) str++; if (!strncmp("add ", str, 4)) return add_l2tp(cfd, str+4); if (!the_l2tpd) { cfd->printf("l2tpd not active for command '%s'\n", str); cfd->done(-2); return 2; } if (!strncmp("dump-num-sessions", str, 17)) { the_l2tpd->dump_num_sessions(cfd); } else if (!strncmp("dump-sessions", str, 13)) { the_l2tpd->dump_sessions(cfd); } else if (!strncmp("start-session ", str, 14)) { the_l2tpd->start_session(cfd, str+14); } else if (!strncmp("start-tunnel ", str, 13)) { the_l2tpd->start_tunnel(cfd, str+13); } else if (!strncmp("dump-multihop", str, 13)) { the_l2tpd->dump_multihop(cfd); } else if (!strncmp("add-multihop ", str, 13)) { the_l2tpd->add_multihop(cfd, str+13); } else if (!strncmp("fixed-local-ip ", str, 15)) { the_l2tpd->fixed_local_ip(cfd, str+15); } else if (!strncmp("dump-full-state", str, 15)) { all_dump_full_state(cfd); } else { cfd->printf("usage: l2tpctl\n"); cfd->printf(" l2tpctl add \n"); cfd->printf(" l2tpctl dump-sessions\n"); cfd->printf(" l2tpctl dump-num-sessions\n"); cfd->printf(" l2tpctl start-tunnel \n"); cfd->printf(" l2tpctl start-session \n"); //cfd->printf(" l2tpctl stop-session tunnel_id session_id\n"); cfd->printf(" l2tpctl dump-multihop\n"); cfd->printf(" l2tpctl add-multihop [--secret=] \n"); cfd->printf(" l2tpctl fixed-local-ip \n"); cfd->printf(" l2tpctl dump-full-state\n"); cfd->done(-2); return 2; } return 0; } void l2tpd_t::add_multihop(ctrlfd_t *cfd, const char *str) { struct sockaddr_in sin; l2tp_peer_t *peer; char *secret = NULL; if (!memcmp("--secret=", str, 9)) { char *next; int len = 0; str += 9; next = strchr(str, ' '); if (next && *next) { len = next - str; next++; } secret = strndup(str, len); str = next; fprintf(stderr, "secret: '%s' remains: '%s'\n", secret, str); } sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(strtoip(str)); sin.sin_port = htons(1701); str = strchr(str, ' '); if (str && *str) str++; if (!str || strchr(str, ' ') || strchr(str, '\t')) { cfd->printf("invalid domain name '%s'\n", str); cfd->done(-1); return; } peer = lookup_multihop_peer(str); if (peer) { cfd->printf("multihop peer '%s' already exists\n", str); cfd->done(-1); return; } peer = find_make_peer(&sin, secret); multihop_domains[nr_multihop] = str = strdup(str); multihop_peers[nr_multihop] = peer; nr_multihop ++; cfd->printf("added multihop entry: '%s' to ", str); cfd->printsin(&sin); cfd->putchar('\n'); cfd->done(0); } void l2tpd_t::fixed_local_ip(ctrlfd_t *cfd, const char *str) { u32 addr = htonl(strtoip(str)); if (addr == (u32)-1) { cfd->printf("ip address '%s'", str); cfd->done(-1); return; } if (iface_fixed_local_ip) cfd->printf("replacing old ip address\n"); iface_fixed_local_ip = htonl(addr); cfd->done(0); } void l2tpd_t::dump_multihop(ctrlfd_t *cfd) { cfd->printf("Number of multihop domains: %d\n", nr_multihop); for (int i=0; iprintf(" [%3d] '%s' ", i, multihop_domains[i]); cfd->printsin(&peer->remote_sin); if (peer->m_secret) cfd->printf(" secret '%s'", peer->m_secret); cfd->putchar('\n'); } cfd->done(0); } l2tp_peer_t *l2tpd_t::lookup_multihop_peer(const char *str) { for (int i=0; multihop_domains[i]; i++) { if (!strcmp(str, multihop_domains[i])) { fprintf(stderr, "multihop %d matched '%s'\n", i, str); return multihop_peers[i]; } } return NULL; } l2tp_tunnel_t *l2tpd_t::try_multihop(OptionSet_t *options) { if (nr_multihop) { const char *str = strchr(options->user, '@'); if (str && *++str) { l2tp_peer_t *peer = lookup_multihop_peer(str); if (peer) return peer->find_avail_tunnel(); } } return NULL; } void l2tpd_t::dump_full_state(ctrlfd_t *cfd) { cfd->printf("add %s\n", iptostr(ntohl(our_sin.sin_addr.s_addr))); dump_sessions(cfd, 1); } void all_dump_full_state(ctrlfd_t *cfd) { l2tpd_t *l2tpd; if (dyn_ip_enabled) cfd->printf("dyn-ip-enable %d\n", dyn_ip_enabled); if (iface_fixed_local_ip) cfd->printf("fixed-local-ip %s\n", iptostr(iface_fixed_local_ip)); for (l2tpd = l2tpd_head; l2tpd; l2tpd = l2tpd->next) l2tpd->dump_full_state(cfd); cfd->done(0); } #endif