/* l2tpd.cc * * Copyright 2004, 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. */ #ifdef USE_L2TP #include "l2tpd.h" #include "l2tp_tunnel.h" #include #include #include #include #include #include #include #include #include #include #include #include "l2tp_linux.h" #include "ctrlfd.h" #include "babd.h" #include "iface.h" #include "bvirt_ns.h" #include "radius.h" #include "multihop.h" multihop_t global_multihop; int l2tp_hard_mru = 1442; int l2tp_hard_mtu = 1442; static l2tpd_t *l2tpd_head, **l2tpd_tailp = &l2tpd_head; static const char *default_host_name = "babylon"; extern l2tp_tunnel_t *l2tp_tunnels[65536]; l2tpd_t::l2tpd_t(ctrlfd_t *cfd, bvirt_ns_t *netns, union sockaddr_union sau, u32 replicate) { ::l2tp_replication_t(); init_failed = 1; host_name = default_host_name; host_name_len = strlen(host_name); first_peer = NULL; peer_must_auth = 0; memset(l2tp_tunnels, 0, sizeof(l2tp_tunnels)); next = NULL; prevp = NULL; l2tp_fd = -1; m_netns = netns; memcpy(&our_sau, &sau, sizeof(our_sau)); udp_fd = netns_socket(netns, sau.sa.sa_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, &sau.sa, sizeof(sau)) < 0) { cfd->perror("l2tpd_t:bind(udp_fd)"); cfd->done(-2); return; // FIXME leak } if (1 && do_userland_only) l2tp_fd = -1; else { l2tp_fd = socket(AF_L2TP, SOCK_DGRAM, 0); if (l2tp_fd < 0) { system("/sbin/modprobe bab_l2tp"); 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 PacketHandler_t *ph = this; ph->SelectSetEvents(udp_fd, SEL_READ); if (replicate) { if (setup_replication(cfd, replicate)) return; } next = NULL; prevp = l2tpd_tailp; *l2tpd_tailp = this; l2tpd_tailp = &this->next; init_failed = 0; cfd->printf("l2tpd started\n"); cfd->done(0); } l2tpd_t::~l2tpd_t(void) { for (unsigned i=0; i<65536; i++) { if (l2tp_tunnels[i]) delete l2tp_tunnels[i]; } while (first_peer) { l2tp_peer_t *next = first_peer->next; delete first_peer; first_peer = next; } if (the_l2tpd == this) the_l2tpd = this->next; if (l2tpd_tailp == &this->next) l2tpd_tailp = prevp; if (prevp) /* we may be partially initialized */ *prevp = this->next; if (next) next->prevp = prevp; next = NULL; prevp = NULL; if (udp_fd != -1) close(udp_fd); if (l2tp_fd != -1) close(l2tp_fd); } void l2tpd_t::handle_packet(char *buf, unsigned size, union sockaddr_union *sau) { if (debug) fprintf(stderr, "l2tpd_t::handle_packet\n"); l2tp_replication_t *repl = this; if (repl->repl_fd != -1) { repl->replicate_packet(buf, size, sau, sizeof(*sau)); return; } l2tp_peer_t *peer = find_make_peer(sau, NULL, m_netns); peer->handle_packet(buf, size, sau); } l2tp_peer_t *l2tpd_t::find_peer(union sockaddr_union *sau, bvirt_ns_t *netns) { l2tp_peer_t *peer = first_peer; while (peer) { if (sau_match(sau, &peer->remote_sau) && (peer->m_netns == netns)) return peer; peer = peer->next; } return NULL; } l2tp_peer_t *l2tpd_t::find_make_peer(union sockaddr_union *sau, const char *secret, bvirt_ns_t *netns) { l2tp_peer_t *peer = find_peer(sau, netns); if (!peer) { l2tpd_t *d = this; if ((m_netns != netns) || (our_sau.sa.sa_family != sau->sa.sa_family)) d = find_local_l2tpd(NULL, netns, sau->sa.sa_family); if (!d) return NULL; peer = new l2tp_peer_t(d, &our_sau, sau, secret, netns); peer->next = first_peer; first_peer = peer; } return peer; } l2tp_tunnel_t *l2tpd_t::make_tunnel(union sockaddr_union *sau, u16 tunnel, bvirt_ns_t *netns) { l2tp_peer_t *peer = find_make_peer(sau, NULL, netns); return peer->make_tunnel(); } void PacketHandler_t::SelectEvent(int fd, SelectEventType event) { union sockaddr_union sau; char buf[2048]; unsigned i; //fprintf(stderr, "PacketHandler_t::SelectEvent\n"); for (i=0; i<100; i++) { int ret; socklen_t addrlen = sizeof(sau); ret = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT | MSG_TRUNC, &sau.sa, &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 ? &sau : NULL); } } void l2tpd_t::dump_sessions(ctrlfd_t *cfd, int verbose) { pid_t child; if (!verbose) { child = fork(); if (child) { if (child < 0) { cfd->perror("fork()"); cfd->done(-2); } delete cfd; return; } } for (l2tp_peer_t *peer = first_peer; peer; peer = peer->next) peer->dump_sessions(cfd, verbose); if (!verbose) { cfd->done(0); _exit(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->my_putchar('\n'); cfd->printf("number of Established tunnels: %u\n", num_est_tunnels); cfd->printf("total allocated tunnels: %u\n", num_tunnels); cfd->done(0); } void l2tpd_t::set_host_name(ctrlfd_t *cfd, const char *name) { if (host_name != default_host_name) free((void *)host_name); host_name = strdup(name); host_name_len = strlen(host_name); cfd->printf("host-name '%s'\n", name); cfd->done(0); } void l2tpd_t::start_tunnel(ctrlfd_t *cfd, char *ip) { union sockaddr_union sau; l2tp_tunnel_t *tunnel; bvirt_ns_t *netns = NULL; if (bvirt_parse_ns(cfd, &ip, &netns)) return; sau_parse(&sau, ip); cfd->printf("creating tunnel\n"); tunnel = make_tunnel(&sau, 0, netns); cfd->printf("local tunnel %d\n", tunnel->tunnel_id); tunnel->open_session(cfd); } void l2tpd_t::stop_tunnel(ctrlfd_t *cfd, char *ip) { bvirt_ns_t *netns = NULL; if (bvirt_parse_ns(cfd, &ip, &netns)) return; union sockaddr_union peer_sau; if (!sau_parse(&peer_sau, ip)) { cfd->printf("invalid ip address '%s'\n", ip); cfd->done(-1); return; } l2tpd_t *d = this; if (netns != m_netns) { d = find_local_l2tpd(NULL, netns, peer_sau.sa.sa_family); if (!d) { cfd->printf("unable to find l2tpd matching netns '%s'", netns->m_name); cfd->done(-1); return; } } unsigned nr_stopped = 0; for (unsigned i = 0; i<65536; i++) { l2tp_tunnel_t *tunnel = d->l2tp_tunnels[i]; if (!tunnel) continue; if (netns && tunnel->peer->m_netns != netns) continue; if (sau_match_ip(&tunnel->peer->remote_sau, &peer_sau)) { if (!tunnel->is_idle()) nr_stopped ++; tunnel->stop_tunnel(); continue; } } cfd->printf("stopped %u tunnels\n", nr_stopped); cfd->done(0); } l2tpd_t *find_local_l2tpd(union sockaddr_union *local_ip, bvirt_ns_t *netns, int family) { l2tpd_t *daemon; if (local_ip) family = local_ip->sa.sa_family; for (daemon = l2tpd_head; daemon; daemon = daemon->next) { if (netns && daemon->m_netns != netns) continue; if (daemon->our_sau.sa.sa_family != family) continue; if (local_ip == NULL) break; if (sau_match_ip(&daemon->our_sau, local_ip)) break; } return daemon; } void l2tpd_t::start_session(ctrlfd_t *cfd, char *ip) { union sockaddr_union local_sau, *local_ip = NULL; union sockaddr_union sau; l2tp_peer_t *peer; l2tp_tunnel_t *tunnel; char *local; l2tpd_t *daemon = this; char *site = NULL; int wait_for_hangup = 0; bvirt_ns_t *netns = NULL; char *called_number = NULL; while (*ip && isspace(*ip)) ip++; if (!strncmp("--help", ip, 6)) { cfd->printf("Usage: l2tpctl start-session [--called-number=] [--wait-for-hangup] [] \n"); cfd->done(-2); return; } if (!strncmp("--called-number=", ip, 16)) { called_number = ip + 16; ip += 16; while (*ip && !isspace(*ip)) ip++; if (*ip) *ip++ = 0; while (*ip && isspace(*ip)) ip++; } if (!strncmp("--wait-for-hangup ", ip, 18)) { wait_for_hangup = 1; ip += 18; } if (*ip && !isdigit(*ip) && !sau_parse(&sau, ip)) { site = ip; while (*ip && !isspace(*ip)) ip++; if (*ip) *ip++ = '\0'; } for (local = ip; *local && *local != ' '; local++) ; if (*local == ' ') *local++ = 0; /* If site contains a ^, it's actually the ip address with a * network namespace specified. */ if (site && strchr(site, '^')) { ip = site; site = NULL; } if (bvirt_parse_ns(cfd, &ip, &netns)) return; if (!sau_parse(&sau, ip)) { cfd->printf("invalid destination ip '%s'\n", ip); cfd->done(-1); return; } if (*local) { bvirt_ns_t *local_netns = NULL; if (bvirt_parse_ns(cfd, &local, &local_netns)) return; if (netns && local_netns && netns != local_netns) { cfd->printf("can't have mismatching netns '%s' vs '%s'\n", netns->m_name, local_netns->m_name); cfd->done(-1); return; } else if (local_netns) netns = local_netns; if (!sau_parse(&local_sau, local)) { cfd->printf("invalid local ip '%s'\n", local); cfd->done(-1); return; } local_ip = &local_sau; } if (local_ip && (local_ip->sa.sa_family != sau.sa.sa_family)) { cfd->printf("Unable to start session -- local and remote addresses must be same family!\n"); cfd->done(-1); return; } if ((local_ip != NULL) || netns) { daemon = find_local_l2tpd(local_ip, netns, -1); if (!daemon) { if (netns) cfd->printf("no matching netns '%s'?\n", netns->m_name); cfd->printf("unable to find local ip '%s'\n", local); cfd->printf("unable to find local ip %s\n", sau_to_str(local_ip)); cfd->done(-1); return; } cfd->printf("local ip %s\n", sau_to_str(&daemon->our_sau)); } peer = daemon->find_peer(&sau, netns); if (!peer) { peer = daemon->find_make_peer(&sau, NULL, netns); if (!peer) { cfd->printf("Unable to create peer\n"); cfd->done(-1); return; } 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, wait_for_hangup, called_number); } void l2tpd_t::cmd_peer_must_auth(ctrlfd_t *cfd, const char *str) { if (*str) peer_must_auth = atoi(str); cfd->printf("peer-must-auth %d\n", peer_must_auth); cfd->done(0); } static void set_peer_secret(ctrlfd_t *cfd, char *str) { char **argv = strtoargv(str); union sockaddr_union local_sau, rem_sau; char *secret; if (!argv || !argv[0] || !argv[1] || !argv[2] || argv[3]) { if (argv) free(argv); cfd->printf("Usage:\n"); cfd->printf(" set-peer-secret \n"); cfd->done(-2); return; } bvirt_ns_t *netns = NULL; /* FIXME */ char *local_ip_str = argv[0]; if (bvirt_parse_ns(cfd, &local_ip_str, &netns)) return; sau_parse(&rem_sau, argv[1]); sau_parse(&local_sau, local_ip_str); secret = argv[2]; free(argv); l2tpd_t *l2tpd = find_local_l2tpd(&local_sau, netns, -1); if (!l2tpd) { cfd->printf("uname to find local l2tpd with ip %s\n", sau_to_str(&local_sau)); cfd->done(-2); return; } l2tp_peer_t *peer = l2tpd->find_peer(&rem_sau, netns); if (peer) { peer->update_secret(strdup(secret)); cfd->printf("updated peer secret successfully\n"); cfd->done(0); return; } peer = l2tpd->find_make_peer(&rem_sau, secret, netns); if (peer) { cfd->printf("made peer secret successfully\n"); cfd->done(0); return; } cfd->printf("couldn't set peer secret! find_make_peer failed\n"); cfd->done(-2); } static void hw_peer(ctrlfd_t *cfd, char *str, bool del) { union sockaddr_union local_sau, rem_sau; char **argv = strtoargv(str); char *hw_dev; if (!argv || !argv[0] || !argv[1] || (!del && (!argv[2] || argv[3])) || (del && argv[2])) { if (argv) free(argv); cfd->printf("Usage:\n"); if (del) cfd->printf(" del-hw-peer \n"); else cfd->printf(" hw-peer \n"); cfd->done(-2); return; } bvirt_ns_t *netns = NULL; /* FIXME */ char *local_ip_str = argv[0]; if (bvirt_parse_ns(cfd, &local_ip_str, &netns)) return; sau_parse(&rem_sau, argv[1]); sau_parse(&local_sau, local_ip_str); hw_dev = argv[2]; free(argv); l2tpd_t *l2tpd = find_local_l2tpd(&local_sau, netns, -1); if (!l2tpd) { cfd->printf("uname to find local l2tpd with ip %s\n", sau_to_str(&local_sau)); cfd->done(-2); return; } l2tp_peer_t *peer = l2tpd->find_peer(&rem_sau, netns); if (peer) { peer->update_hw_dev(hw_dev); cfd->printf("updated peer hw-dev successfully\n"); cfd->printf("peer mac address: %02x:%02x:%02x:%02x:%02x:%02x\n", peer->m_hw_mac[0], peer->m_hw_mac[1], peer->m_hw_mac[2], peer->m_hw_mac[3], peer->m_hw_mac[4], peer->m_hw_mac[5]); cfd->done(0); return; } peer = l2tpd->find_make_peer(&rem_sau, NULL, netns); if (peer) { peer->update_hw_dev(hw_dev); cfd->printf("made peer hw-dev successfully\n"); cfd->printf("peer mac address: %02x:%02x:%02x:%02x:%02x:%02x\n", peer->m_hw_mac[0], peer->m_hw_mac[1], peer->m_hw_mac[2], peer->m_hw_mac[3], peer->m_hw_mac[4], peer->m_hw_mac[5]); cfd->done(0); return; } cfd->printf("couldn't set peer hw-dev! find_make_peer failed\n"); cfd->done(-2); } void l2tpd_t::show_hw_peer(ctrlfd_t *cfd) { for (l2tp_peer_t *peer = first_peer; peer; peer = peer->next) { if (!peer->m_hw_dev) continue; cfd->printf("%s %s %s %c %02x:%02x:%02x:%02x:%02x:%02x\n", sau_to_str(&peer->local_sau), sau_to_str(&peer->remote_sau), peer->m_hw_dev, peer->m_hw_valid ? 'y' : 'n', peer->m_hw_mac[0], peer->m_hw_mac[1], peer->m_hw_mac[2], peer->m_hw_mac[3], peer->m_hw_mac[4], peer->m_hw_mac[5]); } } static void show_hw_peer(ctrlfd_t *cfd) { for (l2tpd_t *l2tpd = l2tpd_head; l2tpd; l2tpd = l2tpd->next) l2tpd->show_hw_peer(cfd); cfd->done(0); } 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; } l2tpd_t *find_l2tpd(bvirt_ns_t *netns, union sockaddr_union *sau) { for (l2tpd_t *l2tpd = l2tpd_head; l2tpd; l2tpd = l2tpd->next) { if (l2tpd->m_netns != netns) continue; if (sau_match(&l2tpd->our_sau, sau)) return l2tpd; } return NULL; } static int add_del_l2tp(ctrlfd_t *cfd, char *str, int do_add) { union sockaddr_union sau; bvirt_ns_t *netns = NULL; u32 replicate_peer = 0; char *peer; if (bvirt_parse_ns(cfd, &str, &netns)) return 2; if (!sau_parse(&sau, str)) { cfd->printf("invalid ip address '%s'\n", str); cfd->done(-2); return 2; } cfd->printf("inet_pton: okay: %s\n", sau_to_str(&sau)); peer = strstr(str, " replicate "); if (peer) { peer += 11; replicate_peer = strtoip(peer); if (replicate_peer == (u32)-1) { cfd->printf("invalid replicate peer ip '%s'\n", peer); cfd->done(-2); return 2; } replicate_peer = htonl(replicate_peer); } l2tpd_t *old = find_l2tpd(netns, &sau); if (!do_add) { /* delete the old daemon */ if (!old) { cfd->printf("Unable to find existing l2tp instance.\n"); cfd->done(-2); return 2; } delete old; cfd->done(0); return 0; } if (old) { cfd->printf("l2tp already listening on '%s%s%s'\n", netns ? netns->m_name : "", netns ? "^" : "", sau_to_str(&sau)); cfd->done(-2); return 2; } l2tpd_t *l2tpd = new l2tpd_t(cfd, netns, sau, replicate_peer); if (l2tpd->init_failed) { delete l2tpd; return 2; } the_l2tpd = l2tpd; return 0; } static void do_hard_mXu(ctrlfd_t *cfd, const char *str, int *hard_mxu, const char *mxu) { int val; val = atoi(str); if (val < 576 || val > 65535) { cfd->printf("invalid mtu/mru (576-65535)\n"); cfd->done(-2); return; } *hard_mxu = val; cfd->printf("changed hard-%s to %d\n", mxu, val); cfd->done(0); } void all_set_bw(ctrlfd_t *cfd, const char *str) { int cmd, arg; int failed = 0; if (!strncmp("credits ", str, 8)) { cmd = BIOC_SET_MAX_CREDITS; str += 8; } else if (!strncmp("bps ", str, 4)) { cmd = BIOC_SET_BANDWIDTH; str += 4; } else if (!strncmp("overhead ", str, 9)) { cmd = BIOC_SET_PACKET_OVERHEAD; str += 9; } else { cfd->printf("unknown command '%s'\n", str); cfd->printf("usage: l2tpctl all-set-bw [credits|bps|overhead] \n"); cfd->done(-2); return; } arg = atoi(str); for (CLink *link = linkListHead; link; link = link->m_next) { int ret = link->ch_ioctl(cmd, arg); if (ret) { cfd->printf("%s: ch_ioctl failed (%d)\n", link->get_name(), ret); failed = 2; } else cfd->printf("%s: ch_ioctl(0x%x, %d): success\n", link->get_name(), cmd, arg); } cfd->done(failed); } void rename_to_ip(ctrlfd_t *cfd, const char *str) { int i = atoi(str); if (i != (i & 1)) { cfd->printf("usage: l2tpctl rename-to-ip <0/1>\n"); cfd->done(-2); return; } iface_rename_to_ip = i; cfd->printf("rename-to-ip %d\n", iface_rename_to_ip); cfd->done(0); } int l2tpd_t::show_running_multihop_config(ctrlfd_t *cfd, int verbose) { global_multihop.show_running_config(cfd, verbose, "--global"); return 0; } int l2tpd_t::show_running_config(ctrlfd_t *cfd, int verbose) { bcluster_show_running_config(cfd, verbose); cfd->printf("add %s%s%s", m_netns ? m_netns->m_name : "", m_netns ? "^" : "", sau_to_str(&our_sau)); l2tp_replication_t *repl = this; if (repl->replicate_peer) cfd->printf(" replicate %s\n", iptostr(ntohl(repl->replicate_peer))); cfd->my_putchar('\n'); if (peer_must_auth || verbose) cfd->printf("peer-must-auth %d\n", peer_must_auth); cfd->printf("host-name %s\n", host_name); cfd->printf("tunnel-csum-payload %u\n", l2tp_default_tunnel_csum_payload); if (first_peer) { for (l2tp_peer_t *peer = first_peer; peer; peer = peer->next) peer->show_running_config(cfd, verbose); } show_running_multihop_config(cfd, verbose); cfd->my_putchar('\n'); return 0; } static void show_running_config(ctrlfd_t *cfd, char *str) { l2tpd_t *daemon; int verbose = 0; if (!strcmp(" -v", str)) verbose = 1; else if (*str) { cfd->printf("usage: l2tpctl show-running-config [-v]"); return; } bvirt_show_running_global_config(cfd); for (daemon = l2tpd_head; daemon; daemon = daemon->next) { if (daemon->show_running_config(cfd, verbose)) return; } show_pppoed_list(cfd, verbose); cfd->printf("# global\n"); if (iface_fixed_local_ip || verbose) cfd->printf("fixed-local-ip %s\n", iptostr(iface_fixed_local_ip)); if (radius_local_ip || verbose) cfd->printf("radius-local-ip %s\n", iptostr(radius_local_ip)); cfd->printf("hard-mtu %u\n", l2tp_hard_mtu); cfd->printf("hard-mru %u\n", l2tp_hard_mru); if (iface_rename_to_ip || verbose) cfd->printf("rename-to-ip %d\n", iface_rename_to_ip); bpolicy_show_running_config(cfd, verbose); bradius_show_running_config(cfd, verbose); cfd->done(0); } void list_tunnels(ctrlfd_t *cfd) { l2tpd_t *daemon; int nr = 0; for (daemon = l2tpd_head; daemon; daemon = daemon->next) { for (int i=0; i<65536; i++) { l2tp_tunnel_t *tunnel = daemon->l2tp_tunnels[i]; if (!tunnel) continue; nr++; cfd->printf("tunnel %d state %s peer %s ", i, tunnel->get_state(), tunnel->peer->peer_ip()); cfd->printf("local %s sessions %d\n", tunnel->peer->local_ip(), 65535 - tunnel->nr_free_session_ids); } } cfd->printf("found %d tunnel%s\n", nr, nr == 1 ? "" : "s"); cfd->done(0); } void l2tpctl_tunnel_csum_payload(ctrlfd_t *cfd, char *str) { l2tpd_t *daemon; int nr = 0; int do_csum = atoi(str); if (do_csum != (do_csum & 1)) { cfd->printf("usage: l2tpctl tunnel-csum-payload <0/1>\n"); cfd->done(-2); return; } l2tp_default_tunnel_csum_payload = do_csum; cfd->printf("tunnel-csum-payload %d\n", do_csum); for (daemon = l2tpd_head; daemon; daemon = daemon->next) { for (int i=0; i<65536; i++) { l2tp_tunnel_t *tunnel = daemon->l2tp_tunnels[i]; if (!tunnel) continue; nr++; tunnel->set_csum_payload(do_csum); } } cfd->printf("set %d tunnel%s\n", nr, nr == 1 ? "" : "s"); cfd->done(0); } static int l2tpctl_hw_session(ctrlfd_t *cfd, char *str) { char *session_str = str; char *next = strchr(str, ' '); if (!next) { cfd->printf("l2tpctl hw-session - missing parameters\n"); cfd->done(-2); return 2; } *next = 0; next++; if (strncmp(session_str, "l2tp", 4)) { cfd->printf("channel '%s' not an L2TP session\n", session_str); cfd->done(-2); return 2; } channel_t *ch = findchan(session_str); if (!ch) { cfd->printf("could not find channel '%s'\n", session_str); cfd->done(-2); return 2; } l2tp_session_t *session = (l2tp_session_t *)ch->link; struct l2tp_hardwire_info info = { "" }; if (0 == strcmp(next, "off")) { int err = session->ch_ioctl(BIOC_L2TP_HARDWIRE_SESSION, (unsigned long)&info); if (err) { cfd->printf("off - error %d\n", err); cfd->done(-2); return 2; } cfd->done(0); return 0; } char *dev = next; next = strchr(next, ' '); if (!next) { cfd->printf("nothing after dev\n"); cfd->done(-2); return 2; } *next = 0; next++; if (strlen(dev) >= sizeof(info.dev_name)) { cfd->printf("dev(%s) too long\n", dev); cfd->done(-2); return 2; } char *src_ip = next; next = strchr(next, ' '); if (!next) { cfd->printf("nothing after src ip\n"); cfd->done(-2); return 2; } *next = 0; next ++; char *sport = strchr(src_ip, ':'); u16 src_port = 1701; if (sport) src_port = atoi(sport + 1); char *dst_ip = next; next = strchr(next, ' '); if (!next) { cfd->printf("nothing after dst ip\n"); cfd->done(-2); return 2; } *next = 0; next ++; char *dport = strchr(dst_ip, ':'); u16 dst_port = 1701; if (dport) dst_port = atoi(dport + 1); char *peer_tunnel = next; next = strchr(next, ' '); if (!next) { cfd->printf("nothing after peer_tunnel\n"); cfd->done(-2); return 2; } *next = 0; next ++; char *peer_session = next; next = strchr(next, ' '); if (!next) { cfd->printf("nothing after peer_session\n"); cfd->done(-2); return 2; } *next = 0; next ++; char *gw_mac = next; next = strchr(next, ' '); if (next) { cfd->printf("garbage(%s) after gw_mac\n", next); cfd->done(-2); return 2; } strcpy(info.dev_name, dev); info.hw_src_ip = htonl(strtoip(src_ip)); info.hw_dst_ip = htonl(strtoip(dst_ip)); info.hw_src_port = htons(src_port); info.hw_dst_port = htons(dst_port); info.hw_peer_tunnel = htons(atoi(peer_tunnel)); info.hw_peer_session = htons(atoi(peer_session)); int mac[6]; if (6 != sscanf(gw_mac, "%x:%x:%x:%x:%x:%x", mac+0, mac+1, mac+2, mac+3, mac+4, mac+5)) { cfd->printf("parse error on mac(%s)\n", gw_mac); cfd->done(-2); return 2; } info.hw_gw_mac[0] = mac[0]; info.hw_gw_mac[1] = mac[1]; info.hw_gw_mac[2] = mac[2]; info.hw_gw_mac[3] = mac[3]; info.hw_gw_mac[4] = mac[4]; info.hw_gw_mac[5] = mac[5]; cfd->printf("hw-session %s %s:%u %s:%u %u %u %02x:%02x:%02x:%02x:%02x:%02x\n", info.dev_name, iptostr(ntohl(info.hw_src_ip)), src_port, iptostr(ntohl(info.hw_dst_ip)), dst_port, ntohs(info.hw_peer_tunnel), ntohs(info.hw_peer_session), info.hw_gw_mac[0], info.hw_gw_mac[1], info.hw_gw_mac[2], info.hw_gw_mac[3], info.hw_gw_mac[4], info.hw_gw_mac[5] ); int err = session->ch_ioctl(BIOC_L2TP_HARDWIRE_SESSION, (unsigned long)&info); if (err) { cfd->printf("hw-session - error %d\n", err); cfd->done(-2); return 2; } cfd->printf("ok!\n"); cfd->done(0); return 0; } void l2tpctl_plugin(ctrlfd_t *cfd, char *path) { void *dl_handle; dl_handle = dlopen(path, RTLD_GLOBAL | RTLD_NOW); if (dl_handle == NULL) { cfd->perror("dlopen failed"); cfd->printf("path %s\n", path); cfd->printf("dlerror = %s\n", dlerror()); cfd->done(-2); return; } cfd->printf("Plugin %s loaded\n", path); typedef int (*babylond_plugin_init_t)(ctrlfd_t *cfd, void *dl_handle); babylond_plugin_init_t babylond_plugin_init; babylond_plugin_init = (babylond_plugin_init_t)dlsym(dl_handle, "babylond_plugin_init"); if (babylond_plugin_init == NULL) { cfd->printf("babylond_plugin_init not found. Unloading.\n"); if (dlclose(dl_handle)) cfd->perror("dlclose error"); cfd->printf("Plugin %s unloaded\n", path); cfd->done(-2); return; } if (babylond_plugin_init(cfd, dl_handle)) dlclose(dl_handle); } int do_l2tpctl(ctrlfd_t *cfd, char *str) { while (isspace(*str)) str++; if (!strncmp("add ", str, 4)) return add_del_l2tp(cfd, str+4, 1); if (!strncmp("del ", str, 4)) return add_del_l2tp(cfd, str+4, 0); if (!strncmp("bvirt ", str, 5)) { do_bvirt(cfd, str+5); return 0; /* FIXME */ } if (!strncmp("bpolicy ", str, 7)) { do_bpolicy(cfd, str+7); return 0; /* FIXME */ } 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 (!strcmp("list-tunnels", str)) { list_tunnels(cfd); } else if (!strncmp("dump-sessions", str, 13)) { the_l2tpd->dump_sessions(cfd); } else if (!strncmp("host-name ", str, 10)) { the_l2tpd->set_host_name(cfd, str+10); } else if (!strncmp("hard-mtu ", str, 9)) { do_hard_mXu(cfd, str+9, &l2tp_hard_mtu, "mtu"); } else if (!strncmp("hard-mru ", str, 9)) { do_hard_mXu(cfd, str+9, &l2tp_hard_mru, "mru"); } else if (!strncmp("bpppoe ", str, 7)) { do_bpppoe(cfd, str+7); } else if (!strncmp("bvirt", str, 5)) { do_bvirt(cfd, str+5); } else if (!strncmp("realm-virt", str, 10)) { do_bvirt(cfd, str+10); } else if (!strncmp("bradius ", str, 8)) { do_bradius(cfd, str+8); } 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("stop-tunnel ", str, 12)) { the_l2tpd->stop_tunnel(cfd, str+12); } else if (!strncmp("dump-multihop", str, 13)) { global_multihop.dump_multihop(cfd); } else if (!strncmp("del-multihop ", str, 13)) { the_l2tpd->add_del_multihop(cfd, str+13, 0); } else if (!strncmp("add-multihop ", str, 13)) { the_l2tpd->add_del_multihop(cfd, str+13, 1); } else if (!strncmp("set-peer-secret ", str, 16)) { set_peer_secret(cfd, str+16); } else if (!strncmp("hw-peer ", str, 8)) { hw_peer(cfd, str+8, false); } else if (!strncmp("del-hw-peer ", str, 12)) { hw_peer(cfd, str+12, true); } else if (!strcmp("show-hw-peer", str)) { show_hw_peer(cfd); } else if (!strncmp("rename-to-ip ", str, 13)) { rename_to_ip(cfd, str+13); } else if (!strncmp("fixed-local-ip ", str, 15)) { the_l2tpd->fixed_local_ip(cfd, str+15); } else if (!strncmp("radius-local-ip ", str, 16)) { the_l2tpd->cmd_radius_local_ip(cfd, str+16); } else if (!strncmp("peer-must-auth", str, 14)) { the_l2tpd->cmd_peer_must_auth(cfd, str+14); } else if (!strncmp("dump-full-state", str, 15)) { all_dump_full_state(cfd, str+15); } else if (!strncmp("all-set-bw ", str, 11)) { all_set_bw(cfd, str+11); } else if (!strncmp("show-running-config", str, 19)) { show_running_config(cfd, str + 19); } else if (!strncmp("bcluster ", str, 9)) { do_bcluster(cfd, str + 9); } else if (!strncmp("monitor ", str, 8)) { l2tpctl_monitor(cfd, str + 8); } else if (!strncmp("hw-session ", str, 11)) { l2tpctl_hw_session(cfd, str + 11); } else if (!strncmp("tunnel-csum-payload ", str, 20)) { l2tpctl_tunnel_csum_payload(cfd, str+20); } else if (!strncmp("plugin ", str, 7)) { l2tpctl_plugin(cfd, str + 7); } else { cfd->printf("usage: l2tpctl\n"); cfd->printf(" l2tpctl add \n"); cfd->printf(" l2tpctl add [replicate ]\n"); cfd->printf(" l2tpctl host-name \n"); cfd->printf(" l2tpctl list-tunnels\n"); cfd->printf(" l2tpctl dump-sessions\n"); cfd->printf(" l2tpctl dump-num-sessions\n"); cfd->printf(" l2tpctl start-tunnel \n"); cfd->printf(" l2tpctl stop-tunnel \n"); cfd->printf(" l2tpctl start-session [--called-number=] [--wait-for-hangup] \n"); //cfd->printf(" l2tpctl stop-session tunnel_id session_id\n"); cfd->printf(" l2tpctl dump-multihop\n"); cfd->printf(" l2tpctl add-multihop [--insert] [--tunnel-group=...] [--secret=] [--lhs=...] \n"); cfd->printf(" l2tpctl del-multihop [--tunnel-group=...] [--secret=] [--lhs=...] \n"); cfd->printf(" l2tpctl fixed-local-ip \n"); cfd->printf(" l2tpctl radius-local-ip \n"); cfd->printf(" l2tpctl dump-full-state\n"); cfd->printf(" l2tpctl hard-mtu \n"); cfd->printf(" l2tpctl hard-mru \n"); cfd->printf(" l2tpctl bradius \n"); cfd->printf(" l2tpctl rename-to-ip <1/0>\n"); cfd->printf(" l2tpctl set-peer-secret \n"); cfd->printf(" l2tpctl peer-must-auth <1/0>\n"); cfd->printf(" l2tpctl all-set-bw [credits|bps|overhead] \n"); cfd->printf(" l2tpctl show-running-config\n"); cfd->printf(" l2tpctl monitor \n"); cfd->printf(" l2tpctl hw-session \n"); cfd->printf(" l2tpctl hw-session off\n"); cfd->printf(" l2tpctl hw-peer \n"); cfd->printf(" l2tpctl show-hw-peer\n"); cfd->printf(" l2tpctl tunnel-csum-payload <1/0>\n"); cfd->printf(" l2tpctl plugin \n"); cfd->done(-2); return 2; } return 0; } void l2tpd_t::add_del_multihop(ctrlfd_t *cfd, char *str, int add) { union sockaddr_union sau; l2tp_peer_t *peer; char *secret = NULL; char *lhs = NULL; char *tunnel_group = NULL; int insert = 0; int global = 0; int found_arg; bvirt_ns_t *netns = NULL; char *next; do { found_arg = 0; if (!memcmp("--global ", str, 9)) { str += 9; global = 1; } if (!memcmp("--insert ", str, 9)) { str += 9; insert = 1; } if (!memcmp("--secret=", str, 9)) { int len = 0; str += 9; next = strchr(str, ' '); if (next && *next) { len = next - str; next++; } secret = strndup(str, len); str = next; } if (!memcmp("--lhs=", str, 6)) { int len = 0; str += 6; next = strchr(str, ' '); if (next && *next) { len = next - str; next++; } lhs = strndup(str, len); str = next; } if (!memcmp("--tunnel-group=", str, 15)) { int len = 0; str += 15; next = strchr(str, ' '); if (next && *next) { len = next - str; next++; } tunnel_group = strndup(str, len); str = next; } } while (found_arg) ; if (!str) { cfd->printf("missing args\n"); cfd->done(-2); goto out_free; } next = strchr(str, ' '); if (next && *next) *next++ = 0; if (bvirt_parse_ns(cfd, &str, &netns)) return; if (!sau_parse(&sau, str)) { cfd->printf("invalid ip address '%s'\n", str); cfd->done(-1); goto out_free; } str = next; if (!str || strchr(str, ' ') || strchr(str, '\t')) { cfd->printf("invalid domain name '%s'\n", str); cfd->done(-1); goto out_free; } peer = global_multihop.lookup_multihop_peer(tunnel_group, lhs, str, insert); if (peer && add && secret) { cfd->printf("multihop peer '%s' '%s' %d already exists; updating secret\n", lhs, str, insert); peer->update_secret(secret); } else if (peer && add) cfd->printf("multihop peer exists, using existing secret\n"); if (!netns) netns = m_netns; if (!peer) { l2tpd_t *daemon = find_local_l2tpd(NULL, netns, sau.sa.sa_family); if (!daemon) { if (netns) cfd->printf("unable to find l2tpd for netns '%s'\n", netns->m_name); else cfd->printf("unable to find l2tpd\n"); cfd->done(-1); goto out_free; } peer = daemon->find_make_peer(&sau, secret, netns); } if (add) global_multihop.add_multihop(cfd, tunnel_group, str, peer, lhs, &sau, insert); else global_multihop.del_multihop(cfd, tunnel_group, str, peer, lhs); out_free: free(lhs); free(tunnel_group); free(secret); } l2tp_tunnel_t *l2tpd_t::try_multihop(const char *user) { return global_multihop.try_multihop(user); } l2tp_tunnel_t *l2tpd_t::try_multihop(OptionSet_t *options) { if (options->tunnel_group_len) return global_multihop.try_multihop_tunnel_group((char *)options->tunnel_group, options->tunnel_group_len); return try_multihop(options->user); } void l2tpd_t::cmd_radius_local_ip(ctrlfd_t *cfd, const char *str) { u32 addr = htonl(strtoip(str)); if (addr == (u32)-1) { cfd->printf("invalid ip address '%s'", str); cfd->done(-1); return; } if (radius_local_ip) cfd->printf("replacing old ip address\n"); radius_local_ip = htonl(addr); 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_full_state(ctrlfd_t *cfd, int do_config) { if (!do_config) goto dump_sessions; cfd->printf("add %s", sau_to_str(&our_sau)); if (replicate_peer) cfd->printf(" replicate %s", iptostr(ntohl(replicate_peer))); cfd->my_putchar('\n'); cfd->printf("peer-must-auth %d\n", peer_must_auth); if (host_name != default_host_name) cfd->printf("host-name %s\n", host_name); dump_sessions: dump_sessions(cfd, 1); dump_clinks(cfd); } void all_dump_full_state(ctrlfd_t *cfd, const char *str) { l2tpd_t *l2tpd; pid_t child; int do_exit = 0; int do_config = 0; if (!strncmp(" --config", str, 9)) { str += 9; do_config = 1; } if (!strcmp(" --exit", str)) do_exit = 1; else if (*str) { cfd->printf("usage: l2tpctl dump-full-state [--config] [--exit]\n"); cfd->done(-2); return; } if (!do_exit && (child = fork())) { if (child < 0) { cfd->perror("fork()"); cfd->done(-2); } delete cfd; return; } if (do_config) { 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)); cfd->printf("hard-mru %d\n", l2tp_hard_mru); cfd->printf("hard-mtu %d\n", l2tp_hard_mtu); if (iface_rename_to_ip) cfd->printf("rename-to-ip %d\n", iface_rename_to_ip); } for (l2tpd = l2tpd_head; l2tpd; l2tpd = l2tpd->next) l2tpd->dump_full_state(cfd, do_config); dfs_dump_interfaces(cfd); cfd->done(0); _exit(0); } #endif