int daemon_main() { network_init(); // (WSAStartup()) register_signals(); sdns::conf.read_config(); try { tcp_thread tcp; eventloop eloop(&tcp); tcp.set_pointers(&eloop); if(sdns::conf[sdns::SDNS_USER] != "") drop_root(sdns::conf[sdns::SDNS_USER]); else dout() << "SDNS_USER not set, not changing uid/gid"; std::thread eventloop_th(std::ref(eloop)); std::thread tcp_th(std::ref(tcp)); while(true) { os_event event = wait_for_os_event(); if(event == os_event::shutdown) break; if(event == os_event::reload) eloop.reread_static_records(); } eloop.stop(); tcp.stop(); eventloop_th.join(); tcp_th.join(); } catch(const e_exception &e) { eout() << "Error at main(): " << e; eout() << "errno was: " << errno << ": " << strerror_rp(errno); return EXIT_FAILURE; } return EXIT_SUCCESS; }
void configuration::sanity_check_values() { if(conf[DTLS_BIND_PORT] == 0 || conf[DTLS_OUTGOING_PORT] == 0) { // use random but persistent ports for these std::ofstream conffile(conf[CONFIG_FILE], std::ios_base::out | std::ios_base::binary | std::ios_base::app); if(conf[DTLS_BIND_PORT] == 0) { uint16_t port = ntohs(get_random_port()); iout() << "Assigned DTLS_BIND_PORT random port " << port; conffile << "\nDTLS_BIND_PORT=" << port << '\n'; assign_value(DTLS_BIND_PORT, port); } if(conf[DTLS_OUTGOING_PORT] == 0) { uint16_t port = ntohs(get_random_port()); iout() << "Assigned DTLS_OUTGOING_PORT random port " << port; conffile << "\nDTLS_OUTGOING_PORT=" << port << '\n'; assign_value(DTLS_OUTGOING_PORT, port); } } if(conf[DTLS_BIND_PORT] == conf[DTLS_OUTGOING_PORT] || conf[DTLS_BIND6_PORT] == conf[DTLS_OUTGOING_PORT]) { eout() << "DTLS_BIND_PORT or DTLS_OUTGOING_PORT not initialized to separate valid ports, these should have been set to random ports during installation." << " You must set DTLS_BIND_PORT and DTLS_OUTGOING_PORT to separate port numbers in the snow configuration file." << " If you have more than one device try to choose different ports for each device."; abort(); } check_port(conf[DTLS_OUTGOING_PORT], "DTLS_OUTGOING_PORT"); check_port(conf[DTLS_BIND_PORT], "DTLS_BIND_PORT"); check_port(conf[DTLS_BIND6_PORT], "DTLS_BIND6_PORT"); check_port(conf[DHT_PORT], "DHT_PORT"); check_port(conf[NAMESERV_PORT], "NAMESERV_PORT"); check_nonzero(conf[NAMESERV_TIMEOUT_SECS], "NAMESERV_TIMEOUT_SECS"); check_nonzero(conf[DTLS_IDLE_TIMEOUT_SECS], "DTLS_IDLE_TIMEOUT_SECS"); check_nonzero(conf[HEARTBEAT_SECONDS], "HEARTBEAT_SECONDS"); check_nonzero(conf[HEARTBEAT_RETRIES], "HEARTBEAT_RETRIES"); if(conf[NAT_IP_GRACE_PERIOD_SECONDS] < 1800) { wout() << "NAT IP grace period of " << conf[NAT_IP_GRACE_PERIOD_SECONDS] << " from configuration file is too short, using minimum grace period of 1800 seconds"; assign_value(NAT_IP_GRACE_PERIOD_SECONDS, 1800); } if(conf[DHT_BOOTSTRAP_TARGET]==0) { wout() << "DHT_BOOTSTRAP_TARGET cannot be zero, using default value"; assign_value(DHT_BOOTSTRAP_TARGET, 6); } if(conf[DHT_MAX_PEERS] <= 3) { wout() << "DHT_MAX_PEERS cannot be " << conf[DHT_MAX_PEERS] << ", must be at least 4, using default value of 99"; assign_value(DHT_MAX_PEERS, 99); } if(conf[NATPOOL_NETMASK_BITS] > 20 || conf[NATPOOL_NETMASK_BITS] < 4) { eout() << "NATPOOL_NETMASK_BITS must be between 4 and 20"; abort(); } uint32_t addr, netmask = ~htonl((1 << (32 - conf[NATPOOL_NETMASK_BITS])) - 1); if(inet_pton(AF_INET, conf[NATPOOL_NETWORK].c_str(), &addr) != 1 || (addr & ~netmask) != 0) { eout() << "NATPOOL_NETWORK/NATPOOL_NETMASK_BITS as " << conf[NATPOOL_NETWORK] << "/" << conf[NATPOOL_NETMASK_BITS] << " is not a valid subnet."; abort(); } if(conf[VIRTUAL_INTERFACE_MTU] > 65535 || conf[VIRTUAL_INTERFACE_MTU] < 576) { eout() << "VIRTUAL_INTERFACE_MTU cannot be " << conf[VIRTUAL_INTERFACE_MTU]; abort(); } }
void check_nonzero(size_t val, std::string str) { if(val==0) { eout() << str << " cannot be zero"; abort(); } }
StateResult handle_client_intro(void *arg) { Handler *h = (Handler*) arg; ClientHandler *ch = (ClientHandler*) h->udata; dout(2, "[%d]: %s running...\n", mypid, __FUNCTION__); reset_clienthandler(ch, RESET_INPUT|RESET_OUTPUT); ch->olen = snprintf(ch->obuf, ch->ocap, "%d welcomes you. Our NOLISTEN status is: [%s]\r\n", mypid, (is_nolisten == 0) ? "Disabled" : "Enabled"); ch->optr = ch->obuf; client_write(h); migrate_state(h, handle_client); if (kmgr->enqueue_kevent(h->fd, EVFILT_READ, EV_ADD|EV_ENABLE, h) < 0) { eout("[%d]: %s failed to enqueue kevent on READ.\n", mypid, __FUNCTION__); return STATE_ERROR; } return STATE_OKAY; }
void check_port(size_t port, std::string str) { if(port == 0 || port > 65535) { eout() << str << " set to invalid value in configuration, a port value must be 1-65535"; abort(); } }
void network_init() { #ifdef WINSOCK dout() << "network_init: doing WSAStartup()"; WSADATA not_interested; int rv = WSAStartup(MAKEWORD(2,2), ¬_interested); if(rv != 0) { eout() << "WSAStartup failed: " << get_windows_errorstr(rv); throw e_check_sock_err("WSAStartup failed", true); } #endif }
void dtls_dispatch::icmp_socket_event(size_t, pvevent event, sock_err err) { if(event & pvevent::error) { eout() << "Error on ICMP socket: " << err; return; } try { dbuf buf(buflist.get()); size_t bytes = sockets[ICMP4_FD].sock.recv(buf.data(), buf.size()); if(bytes > 0) { snow_packet* packet = reinterpret_cast<snow_packet*>(buf.data()); if(validate_packet4_length(packet, bytes)) { dout() << bytes << " byte icmp4 socket packet: " << *packet; if(packet->header.protocol == ipv4_header::ICMP) { icmp_header& icmp = packet->header.transport_header().icmp; if(icmp.icmp_type == icmp_header::DEST_UNREACHABLE && icmp.payload()->header.protocol == ipv4_header::UDP) { const snow_packet* inner_packet = icmp.payload(); if(icmp.code == icmp_header::PACKET_TOO_BIG) { uint16_t mtu = ntohl(icmp.header_data) & 0xffff; // proposed MTU mtu += inner_packet->header.ihl(); set_icmp_pmtu(ip_info(inner_packet->header.src_addr, inner_packet->header.transport_header().udp.src_port), ip_info(inner_packet->header.dst_addr, inner_packet->header.transport_header().udp.dst_port), mtu); } else { icmp_unreachable(ip_info(inner_packet->header.src_addr, inner_packet->header.transport_header().udp.src_port), ip_info(inner_packet->header.dst_addr, inner_packet->header.transport_header().udp.dst_port)); } } } } else { dout() << bytes << " byte ICMP packet failed length validation"; } } else { // (fail) dout() << "0 byte packet from vnet ICMP4 socket"; } buflist.recover(std::move(buf)); } catch(const e_check_sock_err& e) { eout() << "Recv error from ICMP socket: " << e; } }
int main(int argc, char **argv) { std::string config_filename = sdns::conf[sdns::CONFIG_FILE], daemon_name = "sdns"; bool daemonize = false; #ifndef WINDOWS srandom((size_t)(&sdns::conf) + time(nullptr)); for(int opt; (opt = getopt(argc, argv, "c:ds:")) != -1;) { switch(opt) { case 'c': // config filename config_filename = optarg; break; case 'd': // daemonize daemonize = true; break; case 's': // syslog daemon name daemon_name = optarg; default: // '?' std::cerr << "Usage: " << argv[0] << " [-c config_filename] [-d (daemonize)] [-s syslog_daemon_name]" << std::endl; exit(EXIT_FAILURE); } } if(optind < argc) { eout() << "Unexpected argument: " << argv[optind]; exit(EXIT_FAILURE); } #endif sdns::conf.set_config_file(config_filename); try { if(daemonize) return daemon_start(&daemon_main, daemon_name.c_str()); else return daemon_main(); } catch(const e_exception &e) { eout() << "main() caught fatal or unhandled error: " << e; std::cerr << daemon_name << " main() caught fatal or unhandled error: " << e << std::endl; } return EXIT_FAILURE; }
// pinit -> bye bye void dtls_dispatch::remove_peer(const ip_info& local, const ip_info& remote) { dout() << "remove_peer local " << local << " remote " << remote; auto local_it = socket_map.find(local); if(local_it != socket_map.end()) { dtls_socket& sock = sockets[local_it->second]; auto remote_it = sock.peers.find(remote); if(remote_it != sock.peers.end()) { sock.peers.erase(remote_it); if(sock.peers.size() == 0 && (sock.flags & dtls_socket::PERSISTENT) == 0) { dout() << "retiring disused non-persistent socket at idx " << local_it->second; sockets.mark_defunct(local_it->second); socket_map.erase(local_it); } else { dout() << "not removing socket with " << sock.peers.size() << " peers, flags " << sock.flags; } } else { eout() << "BUG: Requested to remove connection with non-existent peer from local " << local << " to remote " << remote; } } else { eout() << "BUG: Requested to remove connection with non-existent socket from local " << local << " to remote " << remote; } }
void dtls_dispatch::icmp6_socket_event(size_t, pvevent event, sock_err err) { if(event & pvevent::error) { eout() << "Error on ICMP socket: " << err; return; } try { dbuf buf(buflist.get()); size_t bytes = sockets[ICMP6_FD].sock.recv(buf.data(), buf.size()); if(bytes > 0) { icmp6_header* packet = reinterpret_cast<icmp6_header*>(buf.data()); if(validate_ipv6_icmp(packet, bytes)) { dout() << bytes << " byte icmp6 socket packet: " << *packet; if(packet->contains_inner_packet() && packet->payload()->header6.next_header == ipv4_header::UDP) { const snow_packet* inner_packet = packet->payload(); if(packet->icmp_type == icmp6_header::PACKET_TOO_BIG) { // this fails to account for extension headers, but 'next_header == UDP' is ignoring packets with them anyway uint16_t mtu = ntohl(packet->header_data)/*proposed MTU*/ + sizeof(ipv6_header); set_icmp_pmtu(ip_info(inner_packet->header6.src_addr, inner_packet->header6.transport_header().udp.src_port), ip_info(inner_packet->header6.dst_addr, inner_packet->header6.transport_header().udp.dst_port), mtu); } else if(packet->icmp_type == icmp6_header::DEST_UNREACHABLE) { icmp_unreachable(ip_info(inner_packet->header6.src_addr, inner_packet->header6.transport_header().udp.src_port), ip_info(inner_packet->header6.dst_addr, inner_packet->header6.transport_header().udp.dst_port)); } } } else { dout() << bytes << " byte ICMP6 packet failed length validation"; } } else { // (fail) dout() << "0 byte packet from vnet ICMP6 socket"; } buflist.recover(std::move(buf)); } catch(const e_check_sock_err& e) { eout() << "Recv error from ICMP6 socket: " << e; } }
void dtls_dispatch::send_holepunch(const sockaddrunion& local, const sockaddrunion& remote) { // send holepunch packet with zero byte payload dout() << "send_holepunch local " << local << " remote " << remote; auto it = socket_map.find(ip_info(local)); if(it != socket_map.end()) { try { sockets[it->second].sock.sendto("", 0, remote); } catch(const check_err_exception &e) { eout() << "dispatch doing UDP holepunch local " << local << " remote " << remote << ": " << e; } } else { dout() << "Could not find local addr " << local << " in socket map for sending holepunch to " << remote; } }
uint16_t get_random_port() { try { csocket sock(AF_INET, SOCK_DGRAM); sockaddrunion su; memset(&su, 0, sizeof(su)); su.sa.sin_family = AF_INET; sock.bind(su); sock.getsockname(su); return su.sa.sin_port; } catch(const e_check_sock_err& e) { eout() << __FILE__ << ":" << __LINE__ << ": Failed to bind port for get_random_port(): " << e; abort(); } return 0; }
tuntap::if_info tuntap::get_if_info() { #ifdef WINDOWS if_info rv; std::string interface_key = "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces\\" + adapter_GUID; std::vector<std::string> addr, mask; try { registry_key adapter(HKEY_LOCAL_MACHINE, interface_key.c_str(), KEY_READ); addr = adapter.values().get_value_multi_string("IPAddress"), mask = adapter.values().get_value_multi_string("SubnetMask"); } catch (const registry_exception &re) { eout() << "Failed to open registry key for TUN/TAP adapter (HKLM\\" << interface_key << "), cannot determine TUN/TAP adapter IP addr/netmask: " << re; throw; } if(addr.size() != mask.size()) throw e_invalid_input("Invalid registry data: Number of IP addresses on virtual interface did not match number of subnet masks"); if(addr.size() == 0) throw e_not_found("No IP address assigned to virtual interface"); if(addr.size() > 1) wout() << "Support for multiple IP addresses on virtual interface not implement, only the first address will be used: " << addr.front(); if(inet_pton(AF_INET, addr.front().c_str(), &rv.if_addr) <= 0) throw check_err_exception("Could not convert virtual interface address string to IP address"); if(inet_pton(AF_INET, mask.front().c_str(), &rv.netmask) <= 0) throw check_err_exception("Could not convert virtual interface netmask string to IP address"); return rv; #else if_info rv; csocket sock(AF_INET, SOCK_DGRAM); // need AF_INET socket for SIOCGIFADDR/SIOCGIFNETMASK ifreq ifr; memset(&ifr, 0, sizeof(ifr)); if(snow::conf[snow::VIRTUAL_INTERFACE].size() >= IFNAMSIZ) throw e_invalid_input("Virtual interface name length is too long"); strcpy(ifr.ifr_name, snow::conf[snow::VIRTUAL_INTERFACE].c_str()); ifr.ifr_addr.sa_family = AF_INET; // get IPv4 addr check_err(ioctl(sock.fd(), SIOCGIFADDR, &ifr), "getting tun/tap interface IP address"); sockaddrunion tmp; tmp.s = ifr.ifr_addr; rv.if_addr = tmp.sa.sin_addr.s_addr; check_err(ioctl(sock.fd(), SIOCGIFNETMASK, &ifr), "getting tun/tap interfacet netmask"); tmp.s = ifr.ifr_addr; rv.netmask = tmp.sa.sin_addr.s_addr; return rv; #endif }
StateResult handle_client(void* arg) { Handler* handler = (Handler*) arg; IoResult ior; if (kresult == NULL) { migrate_state(handler, handle_client_cleanup); if (kmgr->enqueue_pevent(handler) < 0) { eout("[%d]: %s failed to enqueue pevent.\n", mypid, __FUNCTION__); return STATE_ERROR; } dout(2, "[%d]: %s done.\n", mypid, __FUNCTION__); return STATE_OKAY; } switch (kresult->filter) { case EVFILT_READ: dout(2, "[%d]: %s got EVFILT_READ...\n", mypid, __FUNCTION__); ior = client_read(handler); break; case EVFILT_WRITE: dout(2, "[%d]: %s got EVFILT_WRITE...\n", mypid, __FUNCTION__); ior = client_write(handler); break; default: dout(2, "[%d]: %s got unknown filter (%d); closing up shop.\n", mypid, __FUNCTION__, kresult->filter); ior = IO_ERROR; } if (ior < 0) { dout(2, "[%d]: %s got ior %d; close socket.\n", mypid, __FUNCTION__, ior); close(handler->fd); migrate_state(handler, handle_client_cleanup); } dout(2, "[%d]: %s done.\n", mypid, __FUNCTION__); return STATE_OKAY; }
IoResult client_read(Handler *h) { ClientHandler *ch = (ClientHandler*) h->udata; int nread; #if 0 size_t b; size_t bsz = sizeof(b); #endif dout(2, "[%d]: %s running...\n", mypid, __FUNCTION__); if (h->fd < 0) { dout(2, "[%d] %s got dead socket; can't read with that.\n", mypid, __FUNCTION__); return IO_ERROR; } if (kresult->flags & (EV_EOF | EV_ERROR)) { dout(2, "[%d]: %s got EOF/ERROR...\n", mypid, __FUNCTION__); if (kresult->data > 0) { dout(2, "[%d] %s got data to read though! (%d bytes)\n", mypid, __FUNCTION__, kresult->data); goto DoRead; } dout(2, "[%d]: %s returning EOF.\n", mypid, __FUNCTION__); return IO_EOF; } DoRead: dout(2, "[%d]: %s performing read operation on fd %d\n", mypid, __FUNCTION__, h->fd); if ((nread = read(h->fd, ch->iptr, ch->icap - ch->ilen)) < 0) { if (errno == EINTR || errno == EAGAIN) { dout(2, "[%d]: %s got read error of [%d] '%s' (spin again)\n", mypid, __FUNCTION__, errno, strerror(errno)); return IO_OKAY; } dout(2, "[%d]: %s got read error of [%d] '%s' (fatal)\n", mypid, __FUNCTION__, errno, strerror(errno)); return IO_ERROR; } dout(2, "[%d]: %s got %d bytes off of wire.\n", mypid, __FUNCTION__, nread); ch->ilen += nread; ch->iptr += nread; ch->ibuf[ch->ilen] = '\0'; dout(2, "[%d]: %s got %d total bytes so far.\n", mypid, __FUNCTION__, ch->ilen); if (ch->ilen > 1) { if (ch->ibuf[ch->ilen-1] == '\n' && ch->ibuf[ch->ilen-2] == '\r') { dout(2, "[%d]: %s got complete line.\n", mypid, __FUNCTION__); ch->ibuf[ch->ilen-2] = '\0'; ch->ilen -= 2; ch->iptr = &ch->ibuf[0]; if (strcmp(ch->iptr, "listen off") == 0) { // turn the listen socket off. dout(2, "[%d]: %s got command to turn listen OFF.\n", mypid, __FUNCTION__); #if 0 b=1; if (setsockopt(listenfd, SOL_SOCKET,SO_NOLISTEN, &b, bsz) < 0) { eout("[%d]: %s can't set listenfd OFF [%d] %s.\n", mypid, __FUNCTION__, errno, strerror(errno)); } else { dout(2, "[%d]: %s listenfd is OFF; enqueuing on write.\n", mypid, __FUNCTION__); is_nolisten=1; ch->olen = snprintf(ch->obuf, ch->ocap, "[%d]: listenfd is now OFF.\r\n", mypid); ch->optr = ch->obuf; if (kmgr->enqueue_kevent(h->fd, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_ONESHOT, h) < 0) { eout("[%d]: %s failed to enqueue kevent.\n", mypid, __FUNCTION__); return IO_ERROR; } dout(2, "[%d]: %s disabling listenfd from kq.\n", mypid, __FUNCTION__); if (kmgr->enqueue_kevent(listenfd, EVFILT_READ, EV_DISABLE, h) < 0) { eout("[%d]: %s failed to enqueue kevent.\n", mypid, __FUNCTION__); return IO_ERROR; } } #endif } else if (strcmp(ch->iptr, "listen on") == 0) { // turn the listen socket on. dout(2, "[%d]: %s got command to turn listen ON.\n", mypid, __FUNCTION__); #if 0 b=0; if (setsockopt(listenfd, SOL_SOCKET,SO_NOLISTEN, &b, bsz) < 0) { eout("[%d]: %s can't turn listenfd ON [%d] %s.\n", mypid, __FUNCTION__, errno, strerror(errno)); } else { dout(2, "[%d]: %s listenfd is ON; enqueuing on write.\n", mypid, __FUNCTION__); is_nolisten=0; ch->olen = snprintf(ch->obuf, ch->ocap, "[%d]: listenfd is now ON.\r\n", mypid); ch->optr = ch->obuf; if (kmgr->enqueue_kevent(h->fd, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_ONESHOT, h) < 0) { eout("[%d]: %s failed to enqueue kevent.\n", mypid, __FUNCTION__); return IO_ERROR; } dout(2, "[%d]: %s re-enabling listenfd from kq.\n", mypid, __FUNCTION__); if (kmgr->enqueue_kevent(listenfd, EVFILT_READ, EV_ENABLE, h) < 0) { eout("[%d]: %s failed to enqueue kevent.\n", mypid, __FUNCTION__); return IO_ERROR; } } #endif } else if (strcmp(ch->iptr, "listen status") == 0) { dout(2, "[%d]: %s writing out is_nolisten status (%d)\n", mypid, __FUNCTION__, is_nolisten); ch->olen = snprintf(ch->obuf, ch->ocap, "[%d]: listenfd is %s.\r\n", mypid, (is_nolisten == 1) ? "Off" : "On"); ch->optr = ch->obuf; if (kmgr->enqueue_kevent(h->fd, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_ONESHOT, h) < 0) { eout("[%d]: %s failed to enqueue kevent.\n", mypid, __FUNCTION__); return IO_ERROR; } } reset_clienthandler(ch, RESET_INPUT); } } dout(2, "[%d]: %s done.\n", mypid, __FUNCTION__); return IO_OKAY; }
StateResult handle_accept(void* arg) { int connfd; sai_t client_addr; socklen_t caddr_sz = sizeof(client_addr); char ipaddr[INET_ADDRSTRLEN]; Handler *h, *new_h; ClientHandler *new_ch; int incoming=0,i=1; h = (Handler*) arg; incoming = kresult->data; while (incoming) { dout(2, "[%d]: %s processing event %d of %d\n", mypid, __FUNCTION__, i, incoming); if (is_nolisten) { dout(2, "[%d]: **** !!! I'm in NO Listen, but got request! ****\n", mypid); } memset(&client_addr, 0, sizeof(client_addr)); if ((connfd = accept(listenfd, (sa_t*) &client_addr, &caddr_sz)) < 0) { if (errno == EINTR || errno == EAGAIN) { dout(2, "[%d]: accept got '%s'; spin again.\n", mypid, strerror(errno)); return STATE_OKAY; } eout("[%d]: accept failed - [%d] %s\n", errno, strerror(errno)); } if (inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipaddr, INET_ADDRSTRLEN) < 0) { eout("[%d]: can't decode client ip addr.\n"); close(connfd); return STATE_OKAY; } dout(2, "[%d]: got connfd %d { %s, %d }; " "spin up handler...\n", mypid, connfd, ipaddr, ntohs(client_addr.sin_port)); set_fd_nonblock(connfd); new_ch = create_clienthandler(BUF_SZ, BUF_SZ); new_h = create_handler(connfd, handle_client_intro, new_ch); // We first write an intro to the client and then enqueue on read. if (kmgr->enqueue_kevent(connfd, EVFILT_WRITE, EV_ADD|EV_ENABLE|EV_ONESHOT, new_h) < 0) { eout("[%d]: %s failed to enqueue kevent.\n", mypid, handle_accept); return STATE_ERROR; } incoming--; i++; } return STATE_OKAY; }
// 1419 is the mtu using a 1500 byte ethernet mtu - ipv4 header - udp - dtls with default ciphersuite (I think) // TODO: read mtu from interface on each OS instead of hard coding 1419 (also have installer set sensible interface MTU) tuntap::tuntap() : mtu(1419) { // TODO: use configuration parameters for registry key names instead of hard coded values // TODO: package TAP-Windows and change component ID or otherwise figure out how to make it not collide with what OpenVPN uses // see comment in %PROGRAMFILES%\TAP-Windows\driver\OemWin2k.inf after installing OpenVPN TAP driver #ifdef WINDOWS sent_sync = true; // no async send in progress received_sync = 0; memset(&recv_overlapped, 0, sizeof(OVERLAPPED)); memset(&send_overlapped, 0, sizeof(OVERLAPPED)); recv_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); if(recv_event == INVALID_HANDLE_VALUE) throw check_err_exception("CreateEvent failed for tuntap recv_event"); recv_overlapped.hEvent = recv_event; // default callback emits error read_ready_cb = []() { eout() << "BUG: tuntap read ready callback called but not set"; }; if(RegisterWaitForSingleObject(&recv_wait, recv_event, &tuntap::read_event_cb, this, INFINITE, WT_EXECUTEINWAITTHREAD) == FALSE) throw check_err_exception("RegisterWaitForSingleObject on tuntap read event"); adapter_GUID = snow::conf[snow::VIRTUAL_INTERFACE]; if(adapter_GUID == "auto") { try { // open registry key for all network adapters registry_key adapters(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}", KEY_READ); for(auto &it : adapters.subkeys()) { try { registry_key adapter = it.get(KEY_READ); // TODO: change ComponentId from "tap0901" to some other string to remove possible namespace collision with OpenVPN (this has to occur in the driver too) if(adapter.values().get_value_string("ComponentId") == "tap0901") { adapter_GUID = adapter.values().get_value_string("NetCfgInstanceId"); break; } } catch(const registry_exception &re) { dout() << "Failed to access registry subkey: " << re; } } } catch (const registry_exception &re) { eout() << "FATAL: Failed to open network adapters registry key, cannot determine TUN adapter to use: " << re; throw; } } dout() << "Adapter GUID: " << adapter_GUID; if(adapter_GUID == "auto") throw e_not_found("no usable tuntap interface"); // [at this point GUID is value of NetCfgInstanceId, although there could be more than one interface: check all against something else? (interface name?)] // TODO: probably the thing to do is: on install, create a TAP-Windows interface and set its name to 'snow tunnel interface' or so, // then set the GUID in the configuration file [or registry setting], and GUI config program can list interfaces by name and allow user to choose // then the configured GUID is the interface we use and all of this mess doesn't even need to go here -- it just goes once on install and in the config editor // what would then really be useful would be a way (other than changing the ComponentId) to tell other software (e.g. OpenVPN) not to use a particular interface // two possible solutions may be to either make sure that snow starts before the other service [somehow] and gets there first, // or setting permissions [somehow] so that only snow process has access std::string tap_filename("\\\\.\\Global\\"); tap_filename += adapter_GUID + ".tap"; fd = CreateFile(tap_filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0/*no shared access*/, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, nullptr); if(fd == INVALID_HANDLE_VALUE) throw check_err_exception("Failed to open Windows TUN/TAP device"); // name of specific instance of adapter as shown in control panel can be found in: // "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\" + adapter_GUID + "\\Connection" // value is "Name" // set to TUN mode // TAP_WIN_IOCTL_CONFIG_TUN takes three args: interface IP addr, network addr and netmask if_info ifinfo = get_if_info(); uint32_t tun_addrs[3] = { ifinfo.if_addr, ifinfo.if_addr & ifinfo.netmask, ifinfo.netmask }; DWORD rsize; if(DeviceIoControl(fd, TAP_WINDOWS_IOCTL_CONFIG_TUN, tun_addrs, sizeof(tun_addrs), tun_addrs, sizeof(tun_addrs), &rsize, nullptr)) dout() << "TAP-Windows CONFIG_TUN success: " << ss_ipaddr(tun_addrs[0]) << " " << ss_ipaddr(tun_addrs[1]) << " " << ss_ipaddr(tun_addrs[2]); else eout() << "TAP-Windows CONFIG_TUN FAIL: " << ss_ipaddr(tun_addrs[0]) << " " << ss_ipaddr(tun_addrs[1]) << " " << ss_ipaddr(tun_addrs[2]); // set media status as connected ULONG connected = TRUE; if(DeviceIoControl(fd, TAP_WINDOWS_IOCTL_SET_MEDIA_STATUS, &connected, sizeof(connected), &connected, sizeof(connected), &rsize, nullptr) == FALSE) wout() << "Failed to set TAP-Windows media status to connected"; ULONG ifmtu; if(DeviceIoControl(fd, TAP_WINDOWS_IOCTL_GET_MTU, &ifmtu, sizeof(ifmtu), &ifmtu, sizeof(ifmtu), &rsize, nullptr)) { dout() << "Read mtu from TAP-Windows interface: " << ifmtu; if(ifmtu > MIN_PMTU) mtu = ifmtu; } else { wout() << "Failed to get Tap-Windows MTU, assuming default"; } // [flush ARP cache? -> probably unnecessary with tun, but maybe do it anyway? certainly don't want invalid ARP cache entries for pool addrs from before interface startup] // ... also (may) need to undo whatever configuration (remove addrs from if, routes, etc.) on destruction, CloseHandle on fd, set media status to disconnected, etc. #else check_err((fd = open(snow::conf[snow::CLONE_DEVICE].c_str(), O_RDWR)), "opening tun/tap clone device"); ifreq ifr; memset(&ifr, 0, sizeof(ifr)); // check size manually instead of using strncpy: strncpy fails silently and doesn't null terminate if there is insufficient space if(snow::conf[snow::VIRTUAL_INTERFACE].size() >= IFNAMSIZ) throw check_err_exception("VIRTUAL_INTERFACE name is too long", false); strcpy(ifr.ifr_name, snow::conf[snow::VIRTUAL_INTERFACE].c_str()); check_err(fcntl(fd, F_SETFL, O_NONBLOCK), "setting tuntap socket to non-blocking"); #ifdef __linux__ ifr.ifr_flags = IFF_TUN | IFF_NO_PI; check_err(ioctl(fd, TUNSETIFF, (void*) &ifr), "opening tun/tap interface"); #endif // note: Mac and BSD require a stupid hack to make tun behave properly because they insist on a single destination address being specified // solution is to exclude an address from the address pool and specify it as the destination when configuring the interface // then set a route specifying that address as the gateway for the entire snow subnet so OS will send them all to the tun interface // TODO: write code to do that programmatically for Mac/BSD // existing code seems to work on BSD if you set CLONE_DEVICE to e.g. /dev/tun0 and VIRTUAL_INTERFACE to e.g. tun0 and configure the tun interface manually, e.g.: // # ifconfig tun0 create 172.16.0.1 netmask 255.240.0.0 172.31.255.254 mtu 1419 // # route add -net 172.16.0.0 172.31.255.254 255.240.0.0 // (note: this must be done each time you start the daemon, when the daemon exits the interface remains but configuration is forgotten; same two commands w/o "create") // (note: 'ifconfig tun create' will create next tun# necessary, see if there is any simple way to replicate with API) // doing this programmatically requires call to ioctl passing SIOCSIFPHYADDR with in_aliasreq struct as follows: // ifra_name = "tun[#]" // ifra_addr as local interface addr // ifra_dstaddr as fake addr // ifra_mask as subnet mask // and then adding the route via some call to the BSD routing API csocket sock(AF_INET, SOCK_DGRAM); // need ordinary socket for SIOC[GS]*, can't use tun fd check_err(ioctl(sock.fd(), SIOCGIFFLAGS, (void*) &ifr), "getting tuntap interface flags"); if((ifr.ifr_flags & IFF_UP) == 0) { dout() << "tuntap interface was not up, trying to bring it up"; ifr.ifr_flags |= IFF_UP; // make sure interface is up check_err(ioctl(sock.fd(), SIOCSIFFLAGS, (void*) &ifr), "setting tuntap interface flags"); } else { dout() << "tuntap interface was up"; } // there are two possible ways of doing this which are both supported: either snow launches as root or with CAP_NET_ADMIN and sets these here, // or the interface is persistent and configured ahead of time, in which case only ownership of the interface is necessary // so what we do is check that everything is configured correctly and try to fix it if it isn't // that way everything is fine as long as the interface is correctly preconfigured -or- it isn't but we have rights to fix it in_addr addr, netmask; inet_pton(AF_INET, snow::conf[snow::NATPOOL_NETWORK].c_str(), &addr.s_addr); addr.s_addr = htonl(ntohl(addr.s_addr) + 1); netmask.s_addr = ~htonl((1 << (32 - snow::conf[snow::NATPOOL_NETMASK_BITS])) - 1); ifr.ifr_addr.sa_family = AF_INET; // EADDRNOTAVAIL is returned if no address is assigned which just means we have to assign the address, so then ifr_addr will be zero and not match addr int rv = ioctl(sock.fd(), SIOCGIFADDR, &ifr); if(rv < 0 && errno != EADDRNOTAVAIL) throw check_err_exception("getting tun/tap interface IP address"); sockaddrunion su; su.s = ifr.ifr_addr; dout() << "Got tuntap ifaddr " << ss_ipaddr(su.sa.sin_addr.s_addr); if(su.sa.sin_addr.s_addr != addr.s_addr) { dout() << "Virtual interface IP addr was " << ss_ipaddr(su.sa.sin_addr.s_addr) << ", should be " << ss_ipaddr(addr.s_addr) << ", trying to fix"; su.sa.sin_addr = addr; ifr.ifr_addr = su.s; check_err(ioctl(sock.fd(), SIOCSIFADDR, &ifr), "setting tun/tap interface IP address"); } check_err(ioctl(sock.fd(), SIOCGIFNETMASK, &ifr), "getting tun/tap interface netmask"); su.s = ifr.ifr_addr; dout() << "Got tuntap netmask " << ss_ipaddr(su.sa.sin_addr.s_addr); if(su.sa.sin_addr.s_addr != netmask.s_addr) { dout() << "Virtual interface netmask was " << ss_ipaddr(su.sa.sin_addr.s_addr) << ", should be " << ss_ipaddr(netmask.s_addr) << ", trying to fix"; su.sa.sin_addr = netmask; ifr.ifr_addr = su.s; check_err(ioctl(sock.fd(), SIOCSIFNETMASK, &ifr), "setting tun/tap interface netmask"); } mtu = snow::conf[snow::VIRTUAL_INTERFACE_MTU]; check_err(ioctl(sock.fd(), SIOCGIFMTU, (void*) &ifr), "getting tun/tap interface MTU"); dout() << "Existing tuntap interface MTU: " << ifr.ifr_mtu; if(ifr.ifr_mtu < MIN_PMTU || mtu != static_cast<unsigned>(ifr.ifr_mtu)) { dout() << "Virtual interface mtu was " << ifr.ifr_mtu << ", should be " << mtu << ", trying to fix"; ifr.ifr_mtu = mtu; check_err(ioctl(sock.fd(), SIOCSIFMTU, (void*) &ifr), "setting tun/tap interface MTU"); } iout() << "Virtual interface configured with network " << ss_ipaddr(addr.s_addr&netmask.s_addr) << " netmask " << ss_ipaddr(netmask.s_addr) << " address " << ss_ipaddr(addr.s_addr) << " MTU " << mtu; #endif }
csocket::~csocket() { try { close(); } catch(const e_check_sock_err &e) { eout() << "csocket::~csocket():" << e; } }
IoResult client_write(Handler *h) { ClientHandler *ch = (ClientHandler*) h->udata; int nwrite; dout(2, "[%d]: %s running...\n", mypid, __FUNCTION__); if (h->fd < 0) { dout(2, "[%d] %s got dead socket; can't read with that.\n", mypid, __FUNCTION__); return IO_ERROR; } if (kresult->flags & (EV_EOF | EV_ERROR)) { dout(2, "[%d]: %s got EOF/ERROR...bailing out.\n", mypid, __FUNCTION__); // just get outta' here. return IO_EOF; } if (ch->olen <= 0) { dout(2, "[%d] %s has nothing to write (olen: %d)\n", mypid, __FUNCTION__, ch->olen); return IO_OKAY; } dout(2, "[%d]: %s has %d bytes to write.\n", mypid, __FUNCTION__, ch->olen); if ((nwrite = write(h->fd, ch->optr, ch->olen)) < 0) { if (errno == EINTR || errno == EAGAIN) { dout(2, "[%d]: %s got write error of '%s' (spin again)\n", mypid, __FUNCTION__, strerror(errno)); return IO_OKAY; } // something dreadful went wrong. return IO_ERROR; } dout(2, "[%d] %s has written %d bytes onto wire.\n", mypid, __FUNCTION__, nwrite); ch->olen -= nwrite; ch->optr += nwrite; if (ch->olen > 0) { dout(2, "[%d] %s re-nq on write (%d bytes left to write)\n", mypid, __FUNCTION__, ch->olen); // re-enqueue ourselves on write again. if (kmgr->enqueue_kevent(h->fd, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_ONESHOT, h) < 0) { eout("[%d]: %s failed to enqueue kevent.\n", mypid, __FUNCTION__); return IO_ERROR; } return IO_WR_AGAIN; } else { dout(2, "[%d] %s done. no more to bytes to write.\n", mypid, __FUNCTION__); } dout(2, "[%d]: %s done.\n", mypid, __FUNCTION__); return IO_OKAY; }
dtls_dispatch::dtls_dispatch(worker_thread* io) : sockets(std::bind(&dtls_dispatch::cleanup_socket, this, std::placeholders::_1), DISPATCH_NONPEER::NUM_NONPEER_FDS, "dispatch"), buflist(-1), vn(new vnet(this, io, timers, buflist)), pinit(new peer_init(this, vn.get(), timers, &buflist)), run_state(RUNNING), natpmp_addr(0) { buflist.set_bufsize(vn->get_tun_mtu()+200); // tun MTU plus [over-]estimate of DTLS overhead sockets.emplace_back(INVALID_SOCKET, pvevent::read, std::bind(&vnet::tuntap_socket_event, vn.get(), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), dtls_socket(csocket(), sockaddrunion())); #ifdef WINDOWS // set_read_ready_cb must be called after sockets.emplace_back above so that TUNTAP_FD is a valid index, as the callback may be called immediately if data is available vn->get_tun().set_read_ready_cb(std::bind(&decltype(sockets)::indicate_read, &sockets, TUNTAP_FD), vn->get_tun_mtu()); #else sockets.set_fd(TUNTAP_FD, vn->get_tun().get_fd()); #endif sockets.emplace_back(interthread_msg_queue.getSD(), pvevent::read, std::bind(&function_tqueue::pv_execute, &interthread_msg_queue, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), dtls_socket(csocket(), sockaddrunion())); csocket icmp4, icmp6; try { icmp4 = csocket(AF_INET, SOCK_RAW, IPPROTO_ICMP); icmp6 = csocket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); } catch(const check_err_exception& e) { eout() << "Failed to configure ICMP socket: " << e; } int icmp4_sd = icmp4.fd(), icmp6_sd = icmp6.fd(); sockets.emplace_back(icmp4_sd, pvevent::read, std::bind(&dtls_dispatch::icmp_socket_event, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), dtls_socket(std::move(icmp4), sockaddrunion())); sockets.emplace_back(icmp6_sd, pvevent::read, std::bind(&dtls_dispatch::icmp6_socket_event, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), dtls_socket(std::move(icmp6), sockaddrunion())); // that's it for the nonpeers, now grab sockets for all the local ipaddrs peer_socket_event_function = std::bind(&dtls_dispatch::peer_socket_event, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); std::vector<sockaddrunion> local_addrs(detect_local_addrs(vn->get_tun_ipaddr())); // TODO: this concept of always using the same incoming and outgoing local ports for all connections theoretically makes the NAT traversal stuff work pretty well // and it means we can use a finite number of sockets for arbitrarily many peers // but there are probably going to be some circumstances where binding a new socket on a different port will be in order // one example of this is when a connection fails (e.g. hard restart of the daemon or device) and the client tries to reconnect using the same ports, which the peer already has associated with the dead connection // the current implementation probably does the right thing eventually (maybe bad packet causes peer to do heartbeat and then fail the dead connection?) // and if not then the client will send DHT CONNECT which should do the same (induce peer to do heartbeat and fail dead connection) // but that's a lot of timeouts and maybes for something that could be avoided by binding a new socket on a random port // so could do that for the pre-DHT connection for reconnect-after-failure or reconnects during grace period // there are also likely to be situations where the NAT misbehaves in various ways, especially if two nodes behind the same NAT choose the same ports // although in that case the NAPT detection should mostly sort it out in theory (at least for incoming port; still needs to be implemented for outgoing port) // TODO: figure out what to do if we can't bind the appropriate port on one or more addrs // part of the problem is that this sometimes happens normally, e.g. OS (especially stupid Windows) gives useless addrs that don't work but the real addrs are fine // but this could also happen if some other program has the port on one or more addrs, or the port is privileged and we don't have privs, etc. for(sockaddrunion& su : local_addrs) { su.set_ip_port(htons(snow::conf[snow::DTLS_OUTGOING_PORT])); try { create_socket(su, dtls_socket::PERSISTENT); } catch(const e_check_sock_err &e) { eout() << "Could not create create UDP socket: " << e; } if(su.s.sa_family == AF_INET) su.set_ip_port(htons(snow::conf[snow::DTLS_BIND_PORT])); else su.set_ip_port(htons(snow::conf[snow::DTLS_BIND6_PORT])); try { create_socket(su, dtls_socket::PERSISTENT); local_interface_addrs.push_back(su.get_ip_union()); } catch(const e_check_sock_err &e) { eout() << "Could not create create UDP socket: " << e; } } if(local_addrs.size() > 0) { std::vector<ip_info> infos; for(auto& addr : local_addrs) infos.emplace_back(ip_info(addr)); dout() << "local node info: " << pinit->local_hashkey().key_string() << "," << node_info(infos, htons(snow::conf[snow::DTLS_OUTGOING_PORT])); } query_natpmpupnp(); }
int main(int argc, char** argv) { int reqd = 0; int min_reqd = 2; int arg; opterr=0; int result; sai_t addr; char server[1024]; char port[32]; char prog[1024]; char debugconf[1024]; Handler *new_h; AcceptHandler *new_ah; init_output_api(); snprintf(prog,1024, "%s", argv[0]); dout(2, "kqueuer is running...\n"); memset(server,0,1024); memset(port,0,32); memset(prog,0,1024); memset(debugconf,0,1024); while ((arg = getopt(argc, argv, "d:s:p:")) != -1) { switch (arg) { case 's': if (optarg[0] == '-') { fprintf(stderr, "option -s has bad arg (leading '-')\n"); usage(prog); } snprintf(server,1024,"%s",optarg); reqd++; break; case 'p': if (optarg[0] == '-') { fprintf(stderr, "option -p has bad arg (leading '-')\n"); usage(prog); } snprintf(port,32,"%s",optarg); reqd++; break; case 'd': if (optarg[0] == '-') { fprintf(stderr, "option -d has bad arg (leading '-')\n"); usage(prog); } snprintf(debugconf, 1024, "%s", optarg); break; case 'v': version(prog); break; case 'a': about(prog); break; case 'h': default: usage(prog); }; }; argc -= optind; argv += optind; if (min_reqd > reqd) { fprintf(stderr, "Missing arguments. Try -h for more info.\n"); exit(1); } if (debugconf[0] != '\0' && atoi(debugconf) != 0) { set_debug_level(atoi(debugconf)); } else { set_debug_level(2); // default } memset(&addr, 0, sizeof(addr)); if (inet_pton(AF_INET, server, &addr.sin_addr.s_addr) < 0) { eout("kqueuer: failed to get network addr for 192.168.0.71.\n"); return NULL; } addr.sin_family = AF_INET; addr.sin_port = htons(atoi(port)); if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { eout("kqueuer: failed to get socket - [%d] %s\n", errno, strerror(errno)); return NULL; } dout(2, "binding...\n"); if (bind(listenfd, (sa_t*) &addr, sizeof(addr)) < 0) { eout("kqueuer: failed to bind - [%d] %s\n", errno, strerror(errno)); return NULL; } dout(2, "listening...\n"); if (listen(listenfd, 16) < 0) { eout("kqueuer: failed to listen - [%d] %s\n", errno, strerror(errno)); return NULL; } int on=1; socklen_t onsz = sizeof(on); if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, onsz)<0) { eout("kqueuer: setsockopt failed - [%d] %s\n", errno, strerror(errno)); return NULL; } set_fd_nonblock(listenfd); ppid = getpid(); // fork result = rfork(RFPROC|RFNOWAIT|RFFDG); if (result == 0) { mypid = getpid(); dout(2, "I'm the child [PID: %d] (parent: %d)\n", mypid, ppid); } else { mypid = getpid(); dout(2, "I'm the parent [PID: %d]\n", mypid); } // // Both parent and child do the same thing... // is_nolisten = 0; kmgr = new KQueueManager(); // Add the listenfd to the kmgr dout(2, "[%d]: enqueue listenfd %d\n", mypid, listenfd); new_ah = create_accepthandler(); new_h = create_handler(listenfd, handle_accept, new_ah); if (kmgr->enqueue_kevent(listenfd, EVFILT_READ, EV_ADD | EV_ENABLE, new_h) < 0) { eout("[%d]: failed to enqueue kevent.\n", mypid); return NULL; } // Update the kqueue if (kmgr->update_kqueue() < 0) { eout("[%d]: failed to update kqueue.\n", mypid); return NULL; } dout(2, "[%d]: entering main processing loop...\n", mypid); // Enter main processing loop int nevents; kevent_t *cur_kevent; Handler *cur_h; StateResult sr; while (1) { dout(2, "[%d]: waiting (blocked) on kqueue for events...\n", mypid); if ((nevents = kmgr->get_kevents(KQueueManager::KQUEUE_BLOCK)) < 0) { eout("[%d]: get_kevents failed.\n", mypid); return NULL; } dout(2, "[%d]: got %d kevents (is_nolisten: %d).\n", mypid, nevents, is_nolisten); // All events in this kqueue are accept events, so we can handle // each one in the exact same way. while ((cur_kevent = kmgr->get_next_kevent()) != NULL) { dout(2, "[%d]: process event for ident %d\n", mypid, cur_kevent->ident); kresult = cur_kevent; cur_h = (Handler*) cur_kevent->udata; sr = (cur_h->handler)(cur_h); } dout(2, "[%d]: done processing kevents. process pevents...\n", mypid); while ((cur_h = (Handler*) kmgr->get_next_pevent()) != NULL) { dout(2, "[%d]: process pevent for h%d\n", mypid, cur_h->id); sr = (cur_h->handler)(cur_h); } dout(2, "[%d]: done processing pevents. wait for more...\n", mypid); // Update the kqueue if (kmgr->update_kqueue() < 0) { eout("[%d]: failed to update kqueue.\n", mypid); return NULL; } // Update the pending queue if (kmgr->update_pevents() < 0) { eout("[%d]: failed to update pending queue.\n", mypid); return NULL; } } dout(2, "[%d]: all done. goodbye.\n", mypid); return NULL; }