/* l2tp_peer.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. */ #include #include #include #include #include #include #include #include #include #include #include "l2tp_linux.h" #include "l2tpd.h" #include "l2tp_tunnel.h" #include "bvirt_ns.h" l2tp_tunnel_t *l2tp_tunnels[65536]; void make_nonblock(int fd) { int fl = fcntl(fd, F_GETFL); if (-1 == fl) perror("l2tp_tunnel_t: fcntl(F_GETFL)\n"); if (-1 == fcntl(fd, F_SETFL, fl | O_NONBLOCK)) perror("l2tp_tunnel_t: fcntl(F_SETFL)\n"); } l2tp_peer_t::l2tp_peer_t(l2tpd_t *parent, union sockaddr_union *local, union sockaddr_union *remote, const char *secret, bvirt_ns_t *netns) { m_udp_connect_failed = 0; l2tp_tunnels = parent->l2tp_tunnels; l2tpd = parent; next = NULL; m_netns = netns; m_hw_dev = NULL; memset(m_hw_mac, 0, sizeof m_hw_mac); m_hw_valid = false; udp_fd = netns_socket(netns, local->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); if (udp_fd < 0) { perror("socket(l2tp_tunnel_t)"); return; } m_secret = secret ? strdup(secret) : NULL; m_secret_len = secret ? strlen(m_secret) : 0; make_nonblock(udp_fd); local_sau = *local; remote_sau = *remote; int one = 1; if (setsockopt(udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { perror("l2tpd_t:setsockopt(udp_fd, SO_REUSEADDR)"); return; } fprintf(stderr, "remote: %s:%d ", sau_to_str(remote), ntohs(remote->sin.sin_port)); if (bind(udp_fd, &local->sa, sizeof(*local)) < 0) { perror("l2tp_peer_t::l2tp_peer_t: bind(udp)"); return; } /* UDP connect() can fail if there is no route to the peer. Work * around this by performing a connect() later. */ if (connect(udp_fd, &remote->sa, sizeof(*remote)) < 0) { m_udp_connect_failed = 1; perror("connect(l2tp_tunnel_t)"); } union sockaddr_union sau; socklen_t sau_len = sizeof(sau); if (getsockname(udp_fd, &sau.sa, &sau_len) < 0) { perror("getsockbyname(l2tp_tunnel_t)"); return; } local = &sau; fprintf(stderr, "local: %s:%d\n", sau_to_str(local), ntohs(local->sin.sin_port)); /* create and bind l2tp to this new udp socket */ l2tp_fd = netns_socket(netns, AF_L2TP, SOCK_DGRAM, 0); if (l2tp_fd < 0) { perror("socket(l2tpd_t: l2tp)"); if (do_userland_only) this->SelectSetEvents(udp_fd, SEL_READ); return; } make_nonblock(l2tp_fd); struct sockaddr_l2tp l2tp_sa; memset(&l2tp_sa, 0, sizeof(l2tp_sa)); l2tp_sa.sl_family = AF_L2TP; l2tp_sa.sl_rx_sfd = udp_fd; l2tp_sa.sl_tx_sfd = -1; l2tp_sa.sl_tunnel = htons(0); l2tp_sa.sl_session = htons(0); if (bind(l2tp_fd, (struct sockaddr *)&l2tp_sa, sizeof(l2tp_sa))) { perror("l2tp_peer_t: bind(l2tp)"); return; } this->SelectSetEvents(l2tp_fd, SEL_READ); } l2tp_tunnel_t *l2tp_peer_t::make_tunnel(void) { #if 0 /* read back the new tunnel's id */ struct sockaddr_l2tp l2tp_sa; socklen_t sin_len = sizeof(l2tp_sa); if (getsockname(l2tp_fd, (struct sockaddr *)&l2tp_sa, &sin_len) < 0) { perror("getsockbyname(l2tp_tunnel_t:l2tp_fd)"); return; } u16 tunnel_id = ntohs(l2tp_sa.sl_tunnel); //peer_tunnel_id = 0; fprintf(stderr, "tunnel id: %d\n", tunnel_id); #endif return new l2tp_tunnel_t(this, (u16)0); } int l2tp_peer_t::alloc_tunnel_id(l2tp_tunnel_t *tunnel, u16 local, u16 peer) { struct sockaddr_l2tp l2tp_sa; socklen_t sin_len = sizeof(l2tp_sa); int do_rand_tunnel = (local == 0); u16 id; if (l2tp_fd == -1 && do_userland_only) goto do_alloc; if (do_rand_tunnel) get_random((unsigned char *)&local, sizeof local); // Create the new tunnel in the kernel memset(&l2tp_sa, 0, sizeof(l2tp_sa)); l2tp_sa.sl_family = AF_L2TP; l2tp_sa.sl_rx_sfd = -1; l2tp_sa.sl_tx_sfd = udp_fd; l2tp_sa.sl_tunnel = htons(local); l2tp_sa.sl_session = htons(0); l2tp_sa.sl_peer_tunnel = htons(peer); l2tp_sa.sl_peer_session = htons(0); if (bind(l2tp_fd, (struct sockaddr *)&l2tp_sa, sizeof(l2tp_sa))) { perror("l2tp_peer_t::alloc_tunnel_id: bind(l2tp)"); return -1; } if (getsockname(l2tp_fd, (struct sockaddr *)&l2tp_sa, &sin_len) < 0) { perror("getsockbyname(l2tp_tunnel_t:l2tp_fd)"); return -1; } id = ntohs(l2tp_sa.sl_tunnel); if (l2tp_tunnels[id]) fprintf(stderr, "eek! tunnel %d already busy!\n", id); l2tp_tunnels[id] = tunnel; return id; #if 1 do_alloc: get_random((unsigned char *)&id, sizeof id); for (int i=0; i<65536; i++) { if (!id) id = 1; if (!l2tp_tunnels[id]) { l2tp_tunnels[id] = tunnel; return id; #if 0 struct sockaddr_l2tp l2tp_sa; l2tp_sa.sl_family = AF_L2TP; l2tp_sa.sl_sfd = -1; l2tp_sa.sl_tunnel = htons(id); l2tp_sa.sl_session = htons(0); if (!connect(l2tp_fd, (struct sockaddr *)&l2tp_sa, sizeof(l2tp_sa))) { fprintf(stderr, "alloc'd tunnel %d!\n", id); l2tp_tunnels[id] = tunnel; return id; } perror("l2tp_tunnel_t: connect(l2tp)"); #endif } id++; } return -1; #endif } void l2tp_peer_t::remove_tunnel_id(int id, l2tp_tunnel_t *tunnel) { if (l2tp_tunnels[id] == tunnel) { struct sockaddr_l2tp l2tp_sa; l2tp_tunnels[id] = NULL; // Destroy the tunnel in the kernel memset(&l2tp_sa, 0, sizeof(l2tp_sa)); l2tp_sa.sl_family = AF_L2TP; l2tp_sa.sl_rx_sfd = -1; l2tp_sa.sl_tx_sfd = -1; l2tp_sa.sl_tunnel = htons(tunnel->tunnel_id); l2tp_sa.sl_session = htons(0); l2tp_sa.sl_peer_tunnel = htons(0); l2tp_sa.sl_peer_session = htons(0); if (connect(l2tp_fd, (struct sockaddr *)&l2tp_sa, sizeof(l2tp_sa))) perror("l2tp_tunnel_t::remove_tunnel_id connect(l2tp)"); } else fprintf(stderr, "l2tp_peer_t::alloc_tunnel_id -- BUG!\n"); } void l2tp_peer_t::dump_sessions(ctrlfd_t *cfd, int verbose) { unsigned i; cfd->printf("l2tp_peer {\n"); cfd->printf("\tpeer(%s:%d)\n", sau_to_str(&remote_sau), ntohs(remote_sau.sin.sin_port)); // we need a second printf here as inet_ntoa uses a static buffer. cfd->printf("\tlocal(%s:%d)\n", sau_to_str(&local_sau), ntohs(local_sau.sin.sin_port)); if (m_secret_len) cfd->printf("\tsecret(%s)\n", m_secret); if (m_netns) cfd->printf("\tnetns(%s)\n", m_netns->m_name); for (i=0; i<65536; i++) { l2tp_tunnel_t *tunnel = l2tp_tunnels[i]; if (tunnel && tunnel->peer == this) { cfd->printf("\tl2tp_tunnel {\n"); tunnel->dump_sessions(cfd, verbose); cfd->printf("\t}\n"); } } cfd->printf("}\n"); } // l2tp_tunnel = new l2tp_tunnel_t(&our_sin, sin, tunnel); // l2tp_tunnels[l2tp_tunnel->tunnel_id] = l2tp_tunnel; /* handle_packet: * this function should only get called for the first incoming UDP * packet for a tunnel. Its job is to setup a socket specifically * for the new tunnel and kick off the tunnel with the packet. Just * in case, we also pass on any control packets to established tunnel * (this can happen if the packet is queued up in the kernel before * the new tunnel has time to setup the kernel side). */ void l2tp_peer_t::handle_packet(char *buf, unsigned size, union sockaddr_union *sau) { l2tp_tunnel_t *l2tp_tunnel; u16 *data = (u16 *)buf; u16 flags, tunnel, session; if (debug) fprintf(stderr, "l2tp_peer_t::handle_packet\n"); if (pkt.parse_packet(this, (u16 *)buf, size) < 0) { fprintf(stderr, "malformed l2tp packet\n"); return; } flags = ntohs(*data++); if (flags & L2TPF_L) data++; tunnel = ntohs(*data++); session = ntohs(*data++); l2tp_tunnel = l2tp_tunnels[tunnel]; if (!l2tp_tunnel) { #if 0 if (tunnel || session) { fprintf(stderr, "not creating tunnel for %d.%d\n", tunnel, session); return; } #endif // This tunnel doesn't exist. Maybe we're still opening. if (l2tp_tunnels[0]) l2tp_tunnel = l2tp_tunnels[0]; else { if (!(pkt.Flags & L2TPF_T)) return; /* Data packet on non existent tunnel -- drop. */ if (pkt.Ns || pkt.Nr) { if (debug) fprintf(stderr, "not creating tunnel for %d.%d (Ns=%d Nr=%d Flags=0x%04x)\n", tunnel, session, pkt.Ns, pkt.Nr, pkt.Flags); return; } if (!pkt.nr_avps) { fprintf(stderr, "not making tunnel(%d) for ZLB\n", tunnel); return; } u16 msgtype = ntohs(pkt.AVPs[AVP_MessageType][0]); if (msgtype != L2TP_CMSG_TYPE_SCCRQ) { fprintf(stderr, "not making tunnel(%d) for message type %u\n", tunnel, msgtype); return; } if (!tunnel) l2tp_tunnel = make_tunnel(); else l2tp_tunnel = new l2tp_tunnel_t(this, tunnel); } } if (l2tp_tunnel) l2tp_tunnel->handle_packet(&pkt, sau); } l2tp_tunnel_t *l2tp_peer_t::find_next_tunnel(l2tp_tunnel_t *tunnel) { u16 id = 0; if (tunnel) { id = tunnel->tunnel_id + 1; if (!id) return NULL; } do { if (l2tp_tunnels[id] && l2tp_tunnels[id]->peer == this) return l2tp_tunnels[id]; id++; } while (id) ; return NULL; } l2tp_tunnel_t *l2tp_peer_t::find_avail_tunnel(void) { /* Sometimes a route for the peer doesn't exist at startup. Try * to bind now to remedy that. */ if (m_udp_connect_failed) { if (connect(udp_fd, &remote_sau.sa, sizeof(remote_sau)) < 0) perror("l2tp_peer_t::find_avail_tunnel: connect(udp_fd)"); else m_udp_connect_failed = 0; } l2tp_tunnel_t *tunnel; for (tunnel = this->find_next_tunnel(NULL); tunnel; tunnel = this->find_next_tunnel(tunnel)) { if (!tunnel->is_idle() && tunnel->nr_free_session_ids > 0) break; } if (!tunnel) tunnel = this->make_tunnel(); return tunnel; } const char *l2tp_peer_t::peer_ip(void) { return sau_to_str(&remote_sau); } const char *l2tp_peer_t::local_ip(void) { return sau_to_str(&local_sau); } void l2tp_peer_t::update_secret(const char *secret) { m_secret = secret; m_secret_len = secret ? strlen(m_secret) : 0; } void l2tp_peer_t::show_running_config(ctrlfd_t *cfd, int verbose) { if (m_secret) cfd->printf("set-peer-secret %s%s%s %s %s\n", m_netns ? m_netns->m_name : "", m_netns ? "^" : "", sau_to_str(&local_sau), sau_to_str(&remote_sau), m_secret); if (m_hw_dev) cfd->printf("hw-peer %s %s %s\n", sau_to_str(&local_sau), sau_to_str(&remote_sau), m_hw_dev); } static void update_hw_arp_timerfn(void *data) { l2tp_peer_t *peer = (l2tp_peer_t *)data; peer->update_hw_arp(); } void l2tp_peer_t::update_hw_arp(void) { struct arpreq arpreq; bool was_valid = m_hw_valid; bool mac_changed = false; m_hw_valid = false; if (!m_hw_dev) goto out_update; if (remote_sau.sa.sa_family != AF_INET) goto out_update; m_hw_timer.SetFunction(update_hw_arp_timerfn, this); m_hw_timer.Start(400); // check arp every 4 seconds memset(&arpreq, 0, sizeof arpreq); memcpy(&arpreq.arp_pa, &remote_sau, sizeof(struct sockaddr)); strncpy(arpreq.arp_dev, m_hw_dev, sizeof arpreq.arp_dev); if (ioctl(udp_fd, SIOCGARP, &arpreq) != 0) { memset(m_hw_mac, 0, sizeof m_hw_mac); goto out_update; } if (ARPHRD_ETHER == arpreq.arp_ha.sa_family) { if (memcmp(m_hw_mac, &arpreq.arp_ha.sa_data, 6)) { memcpy(m_hw_mac, &arpreq.arp_ha.sa_data, 6); mac_changed = true; } m_hw_valid = true; } out_update: if ((was_valid != m_hw_valid) || mac_changed) { for (unsigned i = 0; i < 65536; i++) { l2tp_tunnel_t *tunnel = l2tp_tunnels[i]; if (!tunnel) continue; struct l2tp_hardwire_info info; get_hw_info(&info); tunnel->update_hw_peer(&info); } } } bool l2tp_peer_t::get_hw_info(struct l2tp_hardwire_info *info) { memset(info, 0, sizeof(*info)); if (!m_hw_valid) return false; if (m_hw_dev) strncpy(info->dev_name, m_hw_dev, sizeof(info->dev_name)); info->hw_src_ip = local_sau.sin.sin_addr.s_addr; info->hw_dst_ip = remote_sau.sin.sin_addr.s_addr; info->hw_src_port = local_sau.sin.sin_port; info->hw_dst_port = remote_sau.sin.sin_port; memcpy(&info->hw_gw_mac, m_hw_mac, 6); return true; } void l2tp_peer_t::update_hw_dev(const char *dev) { m_hw_timer.Stop(); if (dev) dev = strdup(dev); if (m_hw_dev) free((void *)m_hw_dev); m_hw_dev = dev; m_hw_valid = false; update_hw_arp(); }