[tor-commits] [orbot/master] added badvpn as local folder

n8fr8 at torproject.org n8fr8 at torproject.org
Fri Apr 3 17:04:03 UTC 2015


commit 1464901fe3fed48066683d225265fc5deba58d40
Author: SandroB <supp.sandrob at gmail.com>
Date:   Sun Jan 25 12:08:34 2015 +0100

    added badvpn as local folder
---
 external/badvpn_dns/Android.mk                     |   75 +
 external/badvpn_dns/CMakeLists.txt                 |  408 ++
 external/badvpn_dns/COPYING                        |   24 +
 external/badvpn_dns/ChangeLog                      |  216 +
 external/badvpn_dns/INSTALL                        |   76 +
 external/badvpn_dns/INSTALL-WINDOWS                |   72 +
 external/badvpn_dns/arpprobe/BArpProbe.c           |  359 ++
 external/badvpn_dns/arpprobe/BArpProbe.h           |   80 +
 external/badvpn_dns/arpprobe/CMakeLists.txt        |    1 +
 external/badvpn_dns/badvpn.7                       |  324 ++
 external/badvpn_dns/base/BLog.c                    |   96 +
 external/badvpn_dns/base/BLog.h                    |  402 ++
 external/badvpn_dns/base/BLog_syslog.c             |  150 +
 external/badvpn_dns/base/BLog_syslog.h             |   42 +
 external/badvpn_dns/base/BMutex.h                  |  101 +
 external/badvpn_dns/base/BPending.c                |  205 +
 external/badvpn_dns/base/BPending.h                |  250 +
 external/badvpn_dns/base/BPending_list.h           |    4 +
 external/badvpn_dns/base/CMakeLists.txt            |   13 +
 external/badvpn_dns/base/DebugObject.c             |   39 +
 external/badvpn_dns/base/DebugObject.h             |  147 +
 external/badvpn_dns/blog_channels.txt              |  145 +
 external/badvpn_dns/blog_generator/blog.php        |  121 +
 .../badvpn_dns/blog_generator/blog_functions.php   |   35 +
 external/badvpn_dns/bproto/BProto.h                |   85 +
 .../badvpn_dns/bproto_generator/ProtoParser.lime   |   99 +
 .../badvpn_dns/bproto_generator/ProtoParser.php    |  560 +++
 external/badvpn_dns/bproto_generator/bproto.php    |  115 +
 .../bproto_generator/bproto_functions.php          |  777 ++++
 external/badvpn_dns/client/CMakeLists.txt          |   30 +
 external/badvpn_dns/client/DPReceive.c             |  324 ++
 external/badvpn_dns/client/DPReceive.h             |   98 +
 external/badvpn_dns/client/DPRelay.c               |  307 ++
 external/badvpn_dns/client/DPRelay.h               |   89 +
 external/badvpn_dns/client/DataProto.c             |  566 +++
 external/badvpn_dns/client/DataProto.h             |  237 +
 .../badvpn_dns/client/DataProtoKeepaliveSource.c   |   72 +
 .../badvpn_dns/client/DataProtoKeepaliveSource.h   |   73 +
 external/badvpn_dns/client/DatagramPeerIO.c        |  425 ++
 external/badvpn_dns/client/DatagramPeerIO.h        |  271 ++
 .../badvpn_dns/client/FragmentProtoAssembler.c     |  469 ++
 .../badvpn_dns/client/FragmentProtoAssembler.h     |  134 +
 .../client/FragmentProtoAssembler_tree.h           |    9 +
 .../badvpn_dns/client/FragmentProtoDisassembler.c  |  229 +
 .../badvpn_dns/client/FragmentProtoDisassembler.h  |  109 +
 external/badvpn_dns/client/FrameDecider.c          |  795 ++++
 external/badvpn_dns/client/FrameDecider.h          |  196 +
 .../badvpn_dns/client/FrameDecider_groups_tree.h   |    9 +
 .../badvpn_dns/client/FrameDecider_macs_tree.h     |    9 +
 .../client/FrameDecider_multicast_tree.h           |    9 +
 external/badvpn_dns/client/PasswordListener.c      |  374 ++
 external/badvpn_dns/client/PasswordListener.h      |  156 +
 external/badvpn_dns/client/PeerChat.c              |  433 ++
 external/badvpn_dns/client/PeerChat.h              |  123 +
 external/badvpn_dns/client/SCOutmsgEncoder.c       |  104 +
 external/badvpn_dns/client/SCOutmsgEncoder.h       |   76 +
 external/badvpn_dns/client/SPProtoDecoder.c        |  398 ++
 external/badvpn_dns/client/SPProtoDecoder.h        |  171 +
 external/badvpn_dns/client/SPProtoEncoder.c        |  436 ++
 external/badvpn_dns/client/SPProtoEncoder.h        |  172 +
 external/badvpn_dns/client/SimpleStreamBuffer.c    |  144 +
 external/badvpn_dns/client/SimpleStreamBuffer.h    |   52 +
 external/badvpn_dns/client/SinglePacketSource.c    |   85 +
 external/badvpn_dns/client/SinglePacketSource.h    |   73 +
 external/badvpn_dns/client/StreamPeerIO.c          |  712 +++
 external/badvpn_dns/client/StreamPeerIO.h          |  222 +
 external/badvpn_dns/client/badvpn-client.8         |  316 ++
 external/badvpn_dns/client/client.c                | 2997 ++++++++++++
 external/badvpn_dns/client/client.h                |  193 +
 .../badvpn_dns/cmake/modules/COPYING-CMAKE-SCRIPTS |   22 +
 external/badvpn_dns/cmake/modules/FindGLIB2.cmake  |   52 +
 .../cmake/modules/FindLibraryWithDebug.cmake       |  113 +
 external/badvpn_dns/cmake/modules/FindNSPR.cmake   |   57 +
 external/badvpn_dns/cmake/modules/FindNSS.cmake    |   57 +
 .../badvpn_dns/cmake/modules/FindOpenSSL.cmake     |   72 +
 external/badvpn_dns/compile-tun2sock.sh            |  112 +
 external/badvpn_dns/compile-udpgw.sh               |   84 +
 external/badvpn_dns/dhcpclient/BDHCPClient.c       |  340 ++
 external/badvpn_dns/dhcpclient/BDHCPClient.h       |   87 +
 external/badvpn_dns/dhcpclient/BDHCPClientCore.c   |  860 ++++
 external/badvpn_dns/dhcpclient/BDHCPClientCore.h   |  114 +
 external/badvpn_dns/dhcpclient/CMakeLists.txt      |   10 +
 external/badvpn_dns/dhcpclient/DHCPIpUdpDecoder.c  |  137 +
 external/badvpn_dns/dhcpclient/DHCPIpUdpDecoder.h  |   49 +
 external/badvpn_dns/dhcpclient/DHCPIpUdpEncoder.c  |  119 +
 external/badvpn_dns/dhcpclient/DHCPIpUdpEncoder.h  |   49 +
 external/badvpn_dns/dostest/CMakeLists.txt         |   10 +
 external/badvpn_dns/dostest/StreamBuffer.c         |  147 +
 external/badvpn_dns/dostest/StreamBuffer.h         |   70 +
 external/badvpn_dns/dostest/dostest-attacker.c     |  512 ++
 external/badvpn_dns/dostest/dostest-server.c       |  567 +++
 external/badvpn_dns/examples/CMakeLists.txt        |   97 +
 external/badvpn_dns/examples/FastPacketSource.h    |   79 +
 external/badvpn_dns/examples/RandomPacketSink.h    |  116 +
 external/badvpn_dns/examples/TimerPacketSink.h     |   97 +
 external/badvpn_dns/examples/arpprobe_test.c       |  131 +
 external/badvpn_dns/examples/bavl_test.c           |  129 +
 external/badvpn_dns/examples/bencryption_bench.c   |  146 +
 external/badvpn_dns/examples/bprocess_example.c    |  140 +
 external/badvpn_dns/examples/brandom2_test.c       |   65 +
 external/badvpn_dns/examples/btimer_example.c      |   84 +
 external/badvpn_dns/examples/cavl_test.c           |  285 ++
 external/badvpn_dns/examples/cavl_test_tree.h      |   23 +
 external/badvpn_dns/examples/dhcpclient_test.c     |  159 +
 external/badvpn_dns/examples/emscripten_test.c     |   71 +
 external/badvpn_dns/examples/fairqueue_test.c      |  145 +
 external/badvpn_dns/examples/fairqueue_test2.c     |   93 +
 external/badvpn_dns/examples/indexedlist_test.c    |   95 +
 external/badvpn_dns/examples/ipaddr6_test.c        |  169 +
 external/badvpn_dns/examples/ncd_parser_test.c     |  294 ++
 external/badvpn_dns/examples/ncd_tokenizer_test.c  |  149 +
 .../badvpn_dns/examples/ncd_value_parser_test.c    |   78 +
 .../badvpn_dns/examples/ncdinterfacemonitor_test.c |  150 +
 external/badvpn_dns/examples/ncdudevmanager_test.c |  161 +
 external/badvpn_dns/examples/ncdudevmonitor_test.c |  152 +
 external/badvpn_dns/examples/ncdval_test.c         |  380 ++
 external/badvpn_dns/examples/ncdvalcons_test.c     |  111 +
 external/badvpn_dns/examples/parse_number_test.c   |  130 +
 external/badvpn_dns/examples/predicate_test.c      |  116 +
 external/badvpn_dns/examples/savl_test.c           |  135 +
 external/badvpn_dns/examples/savl_test_tree.h      |    9 +
 external/badvpn_dns/examples/stdin_input.c         |  138 +
 external/badvpn_dns/examples/substring_test.c      |  204 +
 external/badvpn_dns/fix_flex.php                   |   10 +
 external/badvpn_dns/flooder/CMakeLists.txt         |    7 +
 external/badvpn_dns/flooder/flooder.c              |  671 +++
 external/badvpn_dns/flooder/flooder.h              |   37 +
 external/badvpn_dns/flow/BufferWriter.c            |  112 +
 external/badvpn_dns/flow/BufferWriter.h            |  107 +
 external/badvpn_dns/flow/CMakeLists.txt            |   31 +
 external/badvpn_dns/flow/LineBuffer.c              |  140 +
 external/badvpn_dns/flow/LineBuffer.h              |   54 +
 external/badvpn_dns/flow/PacketBuffer.c            |  131 +
 external/badvpn_dns/flow/PacketBuffer.h            |   77 +
 external/badvpn_dns/flow/PacketCopier.c            |  136 +
 external/badvpn_dns/flow/PacketCopier.h            |   90 +
 external/badvpn_dns/flow/PacketPassConnector.c     |  125 +
 external/badvpn_dns/flow/PacketPassConnector.h     |  102 +
 external/badvpn_dns/flow/PacketPassFairQueue.c     |  405 ++
 external/badvpn_dns/flow/PacketPassFairQueue.h     |  204 +
 .../badvpn_dns/flow/PacketPassFairQueue_tree.h     |    7 +
 external/badvpn_dns/flow/PacketPassFifoQueue.c     |  241 +
 external/badvpn_dns/flow/PacketPassFifoQueue.h     |   76 +
 external/badvpn_dns/flow/PacketPassInterface.c     |   68 +
 external/badvpn_dns/flow/PacketPassInterface.h     |  236 +
 external/badvpn_dns/flow/PacketPassNotifier.c      |  103 +
 external/badvpn_dns/flow/PacketPassNotifier.h      |   99 +
 external/badvpn_dns/flow/PacketPassPriorityQueue.c |  283 ++
 external/badvpn_dns/flow/PacketPassPriorityQueue.h |  192 +
 .../badvpn_dns/flow/PacketPassPriorityQueue_tree.h |    7 +
 external/badvpn_dns/flow/PacketProtoDecoder.c      |  182 +
 external/badvpn_dns/flow/PacketProtoDecoder.h      |   96 +
 external/badvpn_dns/flow/PacketProtoEncoder.c      |  101 +
 external/badvpn_dns/flow/PacketProtoEncoder.h      |   80 +
 external/badvpn_dns/flow/PacketProtoFlow.c         |   82 +
 external/badvpn_dns/flow/PacketProtoFlow.h         |   83 +
 external/badvpn_dns/flow/PacketRecvBlocker.c       |   99 +
 external/badvpn_dns/flow/PacketRecvBlocker.h       |   90 +
 external/badvpn_dns/flow/PacketRecvConnector.c     |  123 +
 external/badvpn_dns/flow/PacketRecvConnector.h     |  102 +
 external/badvpn_dns/flow/PacketRecvInterface.c     |   56 +
 external/badvpn_dns/flow/PacketRecvInterface.h     |  170 +
 external/badvpn_dns/flow/PacketRouter.c            |  129 +
 external/badvpn_dns/flow/PacketRouter.h            |  126 +
 external/badvpn_dns/flow/PacketStreamSender.c      |  111 +
 external/badvpn_dns/flow/PacketStreamSender.h      |   83 +
 external/badvpn_dns/flow/RouteBuffer.c             |  256 +
 external/badvpn_dns/flow/RouteBuffer.h             |  139 +
 external/badvpn_dns/flow/SinglePacketBuffer.c      |   87 +
 external/badvpn_dns/flow/SinglePacketBuffer.h      |   75 +
 external/badvpn_dns/flow/SinglePacketSender.c      |   72 +
 external/badvpn_dns/flow/SinglePacketSender.h      |   82 +
 external/badvpn_dns/flow/SingleStreamReceiver.c    |   82 +
 external/badvpn_dns/flow/SingleStreamReceiver.h    |   53 +
 external/badvpn_dns/flow/SingleStreamSender.c      |   82 +
 external/badvpn_dns/flow/SingleStreamSender.h      |   53 +
 external/badvpn_dns/flow/StreamPacketSender.c      |   90 +
 external/badvpn_dns/flow/StreamPacketSender.h      |   77 +
 external/badvpn_dns/flow/StreamPassConnector.c     |  120 +
 external/badvpn_dns/flow/StreamPassConnector.h     |   98 +
 external/badvpn_dns/flow/StreamPassInterface.c     |   56 +
 external/badvpn_dns/flow/StreamPassInterface.h     |  165 +
 external/badvpn_dns/flow/StreamRecvConnector.c     |  120 +
 external/badvpn_dns/flow/StreamRecvConnector.h     |   98 +
 external/badvpn_dns/flow/StreamRecvInterface.c     |   56 +
 external/badvpn_dns/flow/StreamRecvInterface.h     |  165 +
 external/badvpn_dns/flowextra/CMakeLists.txt       |    5 +
 external/badvpn_dns/flowextra/KeepaliveIO.c        |  112 +
 external/badvpn_dns/flowextra/KeepaliveIO.h        |   88 +
 .../flowextra/PacketPassInactivityMonitor.c        |  131 +
 .../flowextra/PacketPassInactivityMonitor.h        |  124 +
 external/badvpn_dns/generate_files                 |   51 +
 .../badvpn_dns/generated/NCDConfigParser_parse.c   | 1890 ++++++++
 .../badvpn_dns/generated/NCDConfigParser_parse.h   |   22 +
 .../badvpn_dns/generated/NCDConfigParser_parse.out |  950 ++++
 .../badvpn_dns/generated/NCDConfigParser_parse.y   |  718 +++
 external/badvpn_dns/generated/NCDValParser_parse.c | 1119 +++++
 external/badvpn_dns/generated/NCDValParser_parse.h |    7 +
 .../badvpn_dns/generated/NCDValParser_parse.out    |  217 +
 external/badvpn_dns/generated/NCDValParser_parse.y |  202 +
 external/badvpn_dns/generated/bison_BPredicate.c   | 2168 +++++++++
 external/badvpn_dns/generated/bison_BPredicate.h   |  114 +
 .../badvpn_dns/generated/blog_channel_BArpProbe.h  |    4 +
 .../generated/blog_channel_BConnection.h           |    4 +
 .../generated/blog_channel_BDHCPClient.h           |    4 +
 .../generated/blog_channel_BDHCPClientCore.h       |    4 +
 .../badvpn_dns/generated/blog_channel_BDatagram.h  |    4 +
 .../generated/blog_channel_BEncryption.h           |    4 +
 .../generated/blog_channel_BInputProcess.h         |    4 +
 .../generated/blog_channel_BLockReactor.h          |    4 +
 .../badvpn_dns/generated/blog_channel_BNetwork.h   |    4 +
 .../badvpn_dns/generated/blog_channel_BPredicate.h |    4 +
 .../badvpn_dns/generated/blog_channel_BProcess.h   |    4 +
 .../badvpn_dns/generated/blog_channel_BReactor.h   |    4 +
 .../generated/blog_channel_BSSLConnection.h        |    4 +
 .../badvpn_dns/generated/blog_channel_BSignal.h    |    4 +
 .../generated/blog_channel_BSocksClient.h          |    4 +
 external/badvpn_dns/generated/blog_channel_BTap.h  |    4 +
 .../generated/blog_channel_BThreadSignal.h         |    4 +
 .../generated/blog_channel_BThreadWork.h           |    4 +
 external/badvpn_dns/generated/blog_channel_BTime.h |    4 +
 .../generated/blog_channel_BUnixSignal.h           |    4 +
 .../badvpn_dns/generated/blog_channel_DPReceive.h  |    4 +
 .../badvpn_dns/generated/blog_channel_DPRelay.h    |    4 +
 .../badvpn_dns/generated/blog_channel_DataProto.h  |    4 +
 .../generated/blog_channel_DatagramPeerIO.h        |    4 +
 .../blog_channel_FragmentProtoAssembler.h          |    4 +
 .../generated/blog_channel_FrameDecider.h          |    4 +
 .../badvpn_dns/generated/blog_channel_LineBuffer.h |    4 +
 .../badvpn_dns/generated/blog_channel_Listener.h   |    4 +
 .../generated/blog_channel_NCDBuildProgram.h       |    4 +
 .../generated/blog_channel_NCDConfigParser.h       |    4 +
 .../generated/blog_channel_NCDConfigTokenizer.h    |    4 +
 .../generated/blog_channel_NCDIfConfig.h           |    4 +
 .../generated/blog_channel_NCDInterfaceMonitor.h   |    4 +
 .../generated/blog_channel_NCDModuleIndex.h        |    4 +
 .../generated/blog_channel_NCDModuleProcess.h      |    4 +
 .../generated/blog_channel_NCDPlaceholderDb.h      |    4 +
 .../badvpn_dns/generated/blog_channel_NCDRequest.h |    4 +
 .../generated/blog_channel_NCDRequestClient.h      |    4 +
 .../generated/blog_channel_NCDRfkillMonitor.h      |    4 +
 .../generated/blog_channel_NCDUdevCache.h          |    4 +
 .../generated/blog_channel_NCDUdevManager.h        |    4 +
 .../generated/blog_channel_NCDUdevMonitor.h        |    4 +
 .../generated/blog_channel_NCDUdevMonitorParser.h  |    4 +
 .../badvpn_dns/generated/blog_channel_NCDVal.h     |    4 +
 .../generated/blog_channel_NCDValGenerator.h       |    4 +
 .../generated/blog_channel_NCDValParser.h          |    4 +
 .../generated/blog_channel_PRStreamSink.h          |    4 +
 .../generated/blog_channel_PRStreamSource.h        |    4 +
 .../generated/blog_channel_PacketProtoDecoder.h    |    4 +
 .../generated/blog_channel_PasswordListener.h      |    4 +
 .../badvpn_dns/generated/blog_channel_PeerChat.h   |    4 +
 .../generated/blog_channel_SPProtoDecoder.h        |    4 +
 .../generated/blog_channel_ServerConnection.h      |    4 +
 .../generated/blog_channel_SocksUdpGwClient.h      |    4 +
 .../generated/blog_channel_StreamPeerIO.h          |    4 +
 .../generated/blog_channel_UdpGwClient.h           |    4 +
 external/badvpn_dns/generated/blog_channel_addr.h  |    4 +
 .../badvpn_dns/generated/blog_channel_client.h     |    4 +
 .../generated/blog_channel_dostest_attacker.h      |    4 +
 .../generated/blog_channel_dostest_server.h        |    4 +
 .../badvpn_dns/generated/blog_channel_flooder.h    |    4 +
 external/badvpn_dns/generated/blog_channel_lwip.h  |    4 +
 external/badvpn_dns/generated/blog_channel_ncd.h   |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_alias.h  |    4 +
 .../generated/blog_channel_ncd_arithmetic.h        |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_assert.h |    4 +
 .../generated/blog_channel_ncd_backtrack.h         |    4 +
 .../generated/blog_channel_ncd_blocker.h           |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_buffer.h |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_call2.h  |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_choose.h |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_concat.h |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_daemon.h |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_depend.h |    4 +
 .../generated/blog_channel_ncd_depend_scope.h      |    4 +
 .../generated/blog_channel_ncd_dynamic_depend.h    |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_exit.h   |    4 +
 .../generated/blog_channel_ncd_explode.h           |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_file.h   |    4 +
 .../generated/blog_channel_ncd_file_open.h         |    4 +
 .../generated/blog_channel_ncd_foreach.h           |    4 +
 .../generated/blog_channel_ncd_from_string.h       |    4 +
 .../generated/blog_channel_ncd_getargs.h           |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_getenv.h |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_if.h     |    4 +
 .../generated/blog_channel_ncd_imperative.h        |    4 +
 .../generated/blog_channel_ncd_implode.h           |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_index.h  |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_list.h   |    4 +
 .../generated/blog_channel_ncd_load_module.h       |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_log.h    |    4 +
 .../generated/blog_channel_ncd_log_msg.h           |    4 +
 .../generated/blog_channel_ncd_logical.h           |    4 +
 .../generated/blog_channel_ncd_multidepend.h       |    4 +
 .../blog_channel_ncd_net_backend_badvpn.h          |    4 +
 .../blog_channel_ncd_net_backend_rfkill.h          |    4 +
 .../blog_channel_ncd_net_backend_waitdevice.h      |    4 +
 .../blog_channel_ncd_net_backend_waitlink.h        |    4 +
 .../blog_channel_ncd_net_backend_wpa_supplicant.h  |    4 +
 .../generated/blog_channel_ncd_net_dns.h           |    4 +
 .../generated/blog_channel_ncd_net_iptables.h      |    4 +
 .../generated/blog_channel_ncd_net_ipv4_addr.h     |    4 +
 .../blog_channel_ncd_net_ipv4_addr_in_network.h    |    4 +
 .../blog_channel_ncd_net_ipv4_arp_probe.h          |    4 +
 .../generated/blog_channel_ncd_net_ipv4_dhcp.h     |    4 +
 .../generated/blog_channel_ncd_net_ipv4_route.h    |    4 +
 .../generated/blog_channel_ncd_net_ipv6_addr.h     |    4 +
 .../blog_channel_ncd_net_ipv6_addr_in_network.h    |    4 +
 .../generated/blog_channel_ncd_net_ipv6_route.h    |    4 +
 .../blog_channel_ncd_net_ipv6_wait_dynamic_addr.h  |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_net_up.h |    4 +
 .../blog_channel_ncd_net_watch_interfaces.h        |    4 +
 .../generated/blog_channel_ncd_netmask.h           |    4 +
 .../generated/blog_channel_ncd_ondemand.h          |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_parse.h  |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_print.h  |    4 +
 .../generated/blog_channel_ncd_process_manager.h   |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_reboot.h |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_ref.h    |    4 +
 .../generated/blog_channel_ncd_regex_match.h       |    4 +
 .../generated/blog_channel_ncd_request.h           |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_run.h    |    4 +
 .../generated/blog_channel_ncd_runonce.h           |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_sleep.h  |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_socket.h |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_spawn.h  |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_strcmp.h |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_substr.h |    4 +
 .../generated/blog_channel_ncd_sys_evdev.h         |    4 +
 .../blog_channel_ncd_sys_request_client.h          |    4 +
 .../blog_channel_ncd_sys_request_server.h          |    4 +
 .../generated/blog_channel_ncd_sys_start_process.h |    4 +
 .../blog_channel_ncd_sys_watch_directory.h         |    4 +
 .../generated/blog_channel_ncd_sys_watch_input.h   |    4 +
 .../generated/blog_channel_ncd_sys_watch_usb.h     |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_timer.h  |    4 +
 .../generated/blog_channel_ncd_to_string.h         |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_try.h    |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_value.h  |    4 +
 .../generated/blog_channel_ncd_valuemetic.h        |    4 +
 .../badvpn_dns/generated/blog_channel_ncd_var.h    |    4 +
 .../badvpn_dns/generated/blog_channel_nsskey.h     |    4 +
 .../badvpn_dns/generated/blog_channel_server.h     |    4 +
 .../badvpn_dns/generated/blog_channel_tun2socks.h  |    4 +
 external/badvpn_dns/generated/blog_channel_udpgw.h |    4 +
 .../badvpn_dns/generated/blog_channels_defines.h   |  146 +
 external/badvpn_dns/generated/blog_channels_list.h |  145 +
 external/badvpn_dns/generated/bproto_addr.h        |  675 +++
 external/badvpn_dns/generated/bproto_bproto_test.h | 1029 ++++
 external/badvpn_dns/generated/bproto_msgproto.h    | 2122 +++++++++
 external/badvpn_dns/generated/flex_BPredicate.c    | 2143 +++++++++
 external/badvpn_dns/generated/flex_BPredicate.h    |  350 ++
 external/badvpn_dns/lemon/lemon.c                  | 4889 ++++++++++++++++++++
 external/badvpn_dns/lemon/lempar.c                 |  842 ++++
 external/badvpn_dns/lime/HOWTO                     |   70 +
 external/badvpn_dns/lime/flex_token_stream.php     |   34 +
 external/badvpn_dns/lime/lemon.c                   | 4588 ++++++++++++++++++
 external/badvpn_dns/lime/lime.bootstrap            |   31 +
 external/badvpn_dns/lime/lime.php                  |  910 ++++
 external/badvpn_dns/lime/lime_scan_tokens.l        |  121 +
 external/badvpn_dns/lime/metagrammar               |   58 +
 external/badvpn_dns/lime/parse_engine.php          |  252 +
 external/badvpn_dns/lime/set.so.php                |   29 +
 external/badvpn_dns/lwip/CHANGELOG                 | 3396 ++++++++++++++
 external/badvpn_dns/lwip/CMakeLists.txt            |   27 +
 external/badvpn_dns/lwip/COPYING                   |   33 +
 external/badvpn_dns/lwip/FILES                     |    4 +
 external/badvpn_dns/lwip/README                    |   89 +
 external/badvpn_dns/lwip/UPGRADING                 |  144 +
 external/badvpn_dns/lwip/custom/arch/cc.h          |   96 +
 external/badvpn_dns/lwip/custom/arch/perf.h        |   36 +
 external/badvpn_dns/lwip/custom/lwipopts.h         |   70 +
 external/badvpn_dns/lwip/custom/sys.c              |   37 +
 external/badvpn_dns/lwip/doc/FILES                 |    6 +
 external/badvpn_dns/lwip/doc/contrib.txt           |   63 +
 external/badvpn_dns/lwip/doc/rawapi.txt            |  511 ++
 external/badvpn_dns/lwip/doc/savannah.txt          |  135 +
 external/badvpn_dns/lwip/doc/snmp_agent.txt        |  181 +
 external/badvpn_dns/lwip/doc/sys_arch.txt          |  267 ++
 external/badvpn_dns/lwip/lwip-base-version         |    1 +
 external/badvpn_dns/lwip/src/FILES                 |   13 +
 external/badvpn_dns/lwip/src/api/api_lib.c         |  788 ++++
 external/badvpn_dns/lwip/src/api/api_msg.c         | 1610 +++++++
 external/badvpn_dns/lwip/src/api/err.c             |   75 +
 external/badvpn_dns/lwip/src/api/netbuf.c          |  245 +
 external/badvpn_dns/lwip/src/api/netdb.c           |  353 ++
 external/badvpn_dns/lwip/src/api/netifapi.c        |  160 +
 external/badvpn_dns/lwip/src/api/sockets.c         | 2555 ++++++++++
 external/badvpn_dns/lwip/src/api/tcpip.c           |  492 ++
 external/badvpn_dns/lwip/src/core/def.c            |  108 +
 external/badvpn_dns/lwip/src/core/dhcp.c           | 1771 +++++++
 external/badvpn_dns/lwip/src/core/dns.c            |  988 ++++
 external/badvpn_dns/lwip/src/core/inet_chksum.c    |  545 +++
 external/badvpn_dns/lwip/src/core/init.c           |  345 ++
 external/badvpn_dns/lwip/src/core/ipv4/autoip.c    |  528 +++
 external/badvpn_dns/lwip/src/core/ipv4/icmp.c      |  338 ++
 external/badvpn_dns/lwip/src/core/ipv4/igmp.c      |  805 ++++
 external/badvpn_dns/lwip/src/core/ipv4/ip4.c       |  924 ++++
 external/badvpn_dns/lwip/src/core/ipv4/ip4_addr.c  |  312 ++
 external/badvpn_dns/lwip/src/core/ipv4/ip_frag.c   |  863 ++++
 external/badvpn_dns/lwip/src/core/ipv6/README      |    1 +
 external/badvpn_dns/lwip/src/core/ipv6/dhcp6.c     |   50 +
 external/badvpn_dns/lwip/src/core/ipv6/ethip6.c    |  193 +
 external/badvpn_dns/lwip/src/core/ipv6/icmp6.c     |  337 ++
 external/badvpn_dns/lwip/src/core/ipv6/inet6.c     |   51 +
 external/badvpn_dns/lwip/src/core/ipv6/ip6.c       | 1034 +++++
 external/badvpn_dns/lwip/src/core/ipv6/ip6_addr.c  |  251 +
 external/badvpn_dns/lwip/src/core/ipv6/ip6_frag.c  |  697 +++
 external/badvpn_dns/lwip/src/core/ipv6/mld6.c      |  580 +++
 external/badvpn_dns/lwip/src/core/ipv6/nd6.c       | 1787 +++++++
 external/badvpn_dns/lwip/src/core/mem.c            |  659 +++
 external/badvpn_dns/lwip/src/core/memp.c           |  485 ++
 external/badvpn_dns/lwip/src/core/netif.c          |  918 ++++
 external/badvpn_dns/lwip/src/core/pbuf.c           | 1179 +++++
 external/badvpn_dns/lwip/src/core/raw.c            |  422 ++
 external/badvpn_dns/lwip/src/core/snmp/asn1_dec.c  |  657 +++
 external/badvpn_dns/lwip/src/core/snmp/asn1_enc.c  |  611 +++
 external/badvpn_dns/lwip/src/core/snmp/mib2.c      | 4146 +++++++++++++++++
 .../badvpn_dns/lwip/src/core/snmp/mib_structs.c    | 1174 +++++
 external/badvpn_dns/lwip/src/core/snmp/msg_in.c    | 1453 ++++++
 external/badvpn_dns/lwip/src/core/snmp/msg_out.c   |  678 +++
 external/badvpn_dns/lwip/src/core/stats.c          |  181 +
 external/badvpn_dns/lwip/src/core/sys.c            |   68 +
 external/badvpn_dns/lwip/src/core/tcp.c            | 1852 ++++++++
 external/badvpn_dns/lwip/src/core/tcp_in.c         | 1666 +++++++
 external/badvpn_dns/lwip/src/core/tcp_out.c        | 1499 ++++++
 external/badvpn_dns/lwip/src/core/timers.c         |  546 +++
 external/badvpn_dns/lwip/src/core/udp.c            | 1151 +++++
 .../badvpn_dns/lwip/src/include/ipv4/lwip/autoip.h |  118 +
 .../badvpn_dns/lwip/src/include/ipv4/lwip/icmp.h   |  125 +
 .../badvpn_dns/lwip/src/include/ipv4/lwip/igmp.h   |  106 +
 .../badvpn_dns/lwip/src/include/ipv4/lwip/inet.h   |  107 +
 .../badvpn_dns/lwip/src/include/ipv4/lwip/ip4.h    |  146 +
 .../lwip/src/include/ipv4/lwip/ip4_addr.h          |  244 +
 .../lwip/src/include/ipv4/lwip/ip_frag.h           |   91 +
 .../badvpn_dns/lwip/src/include/ipv6/lwip/dhcp6.h  |   58 +
 .../badvpn_dns/lwip/src/include/ipv6/lwip/ethip6.h |   68 +
 .../badvpn_dns/lwip/src/include/ipv6/lwip/icmp6.h  |  152 +
 .../badvpn_dns/lwip/src/include/ipv6/lwip/inet6.h  |   92 +
 .../badvpn_dns/lwip/src/include/ipv6/lwip/ip6.h    |  197 +
 .../lwip/src/include/ipv6/lwip/ip6_addr.h          |  286 ++
 .../lwip/src/include/ipv6/lwip/ip6_frag.h          |  102 +
 .../badvpn_dns/lwip/src/include/ipv6/lwip/mld6.h   |  118 +
 .../badvpn_dns/lwip/src/include/ipv6/lwip/nd6.h    |  369 ++
 external/badvpn_dns/lwip/src/include/lwip/api.h    |  338 ++
 .../badvpn_dns/lwip/src/include/lwip/api_msg.h     |  177 +
 external/badvpn_dns/lwip/src/include/lwip/arch.h   |  217 +
 external/badvpn_dns/lwip/src/include/lwip/debug.h  |   99 +
 external/badvpn_dns/lwip/src/include/lwip/def.h    |  123 +
 external/badvpn_dns/lwip/src/include/lwip/dhcp.h   |  242 +
 external/badvpn_dns/lwip/src/include/lwip/dns.h    |  124 +
 external/badvpn_dns/lwip/src/include/lwip/err.h    |   85 +
 .../badvpn_dns/lwip/src/include/lwip/inet_chksum.h |  112 +
 external/badvpn_dns/lwip/src/include/lwip/init.h   |   72 +
 external/badvpn_dns/lwip/src/include/lwip/ip.h     |  254 +
 .../badvpn_dns/lwip/src/include/lwip/ip_addr.h     |  130 +
 external/badvpn_dns/lwip/src/include/lwip/mem.h    |  123 +
 external/badvpn_dns/lwip/src/include/lwip/memp.h   |  116 +
 .../badvpn_dns/lwip/src/include/lwip/memp_std.h    |  135 +
 external/badvpn_dns/lwip/src/include/lwip/netbuf.h |  112 +
 external/badvpn_dns/lwip/src/include/lwip/netdb.h  |  124 +
 external/badvpn_dns/lwip/src/include/lwip/netif.h  |  393 ++
 .../badvpn_dns/lwip/src/include/lwip/netifapi.h    |  108 +
 external/badvpn_dns/lwip/src/include/lwip/opt.h    | 2417 ++++++++++
 external/badvpn_dns/lwip/src/include/lwip/pbuf.h   |  185 +
 external/badvpn_dns/lwip/src/include/lwip/raw.h    |  131 +
 external/badvpn_dns/lwip/src/include/lwip/sio.h    |  141 +
 external/badvpn_dns/lwip/src/include/lwip/snmp.h   |  367 ++
 .../badvpn_dns/lwip/src/include/lwip/snmp_asn1.h   |  101 +
 .../badvpn_dns/lwip/src/include/lwip/snmp_msg.h    |  315 ++
 .../lwip/src/include/lwip/snmp_structs.h           |  268 ++
 .../badvpn_dns/lwip/src/include/lwip/sockets.h     |  411 ++
 external/badvpn_dns/lwip/src/include/lwip/stats.h  |  347 ++
 external/badvpn_dns/lwip/src/include/lwip/sys.h    |  336 ++
 external/badvpn_dns/lwip/src/include/lwip/tcp.h    |  400 ++
 .../badvpn_dns/lwip/src/include/lwip/tcp_impl.h    |  508 ++
 external/badvpn_dns/lwip/src/include/lwip/tcpip.h  |  179 +
 external/badvpn_dns/lwip/src/include/lwip/timers.h |  100 +
 external/badvpn_dns/lwip/src/include/lwip/udp.h    |  215 +
 .../badvpn_dns/lwip/src/include/netif/etharp.h     |  223 +
 .../badvpn_dns/lwip/src/include/netif/ppp_oe.h     |  190 +
 .../badvpn_dns/lwip/src/include/netif/slipif.h     |   81 +
 external/badvpn_dns/lwip/src/include/posix/netdb.h |   33 +
 .../badvpn_dns/lwip/src/include/posix/sys/socket.h |   33 +
 external/badvpn_dns/lwip/src/netif/FILES           |   29 +
 external/badvpn_dns/lwip/src/netif/etharp.c        | 1413 ++++++
 external/badvpn_dns/lwip/src/netif/ethernetif.c    |  322 ++
 external/badvpn_dns/lwip/src/netif/ppp/auth.c      | 1334 ++++++
 external/badvpn_dns/lwip/src/netif/ppp/auth.h      |  111 +
 external/badvpn_dns/lwip/src/netif/ppp/chap.c      |  908 ++++
 external/badvpn_dns/lwip/src/netif/ppp/chap.h      |  150 +
 external/badvpn_dns/lwip/src/netif/ppp/chpms.c     |  396 ++
 external/badvpn_dns/lwip/src/netif/ppp/chpms.h     |   64 +
 external/badvpn_dns/lwip/src/netif/ppp/fsm.c       |  890 ++++
 external/badvpn_dns/lwip/src/netif/ppp/fsm.h       |  157 +
 external/badvpn_dns/lwip/src/netif/ppp/ipcp.c      | 1411 ++++++
 external/badvpn_dns/lwip/src/netif/ppp/ipcp.h      |  106 +
 external/badvpn_dns/lwip/src/netif/ppp/lcp.c       | 2066 +++++++++
 external/badvpn_dns/lwip/src/netif/ppp/lcp.h       |  151 +
 external/badvpn_dns/lwip/src/netif/ppp/magic.c     |   80 +
 external/badvpn_dns/lwip/src/netif/ppp/magic.h     |   63 +
 external/badvpn_dns/lwip/src/netif/ppp/md5.c       |  320 ++
 external/badvpn_dns/lwip/src/netif/ppp/md5.h       |   55 +
 external/badvpn_dns/lwip/src/netif/ppp/pap.c       |  628 +++
 external/badvpn_dns/lwip/src/netif/ppp/pap.h       |  118 +
 external/badvpn_dns/lwip/src/netif/ppp/ppp.c       | 2052 ++++++++
 external/badvpn_dns/lwip/src/netif/ppp/ppp.h       |  201 +
 external/badvpn_dns/lwip/src/netif/ppp/ppp_impl.h  |  363 ++
 external/badvpn_dns/lwip/src/netif/ppp/ppp_oe.c    | 1132 +++++
 external/badvpn_dns/lwip/src/netif/ppp/pppdebug.h  |   73 +
 external/badvpn_dns/lwip/src/netif/ppp/randm.c     |  249 +
 external/badvpn_dns/lwip/src/netif/ppp/randm.h     |   81 +
 external/badvpn_dns/lwip/src/netif/ppp/readme.txt  |   21 +
 external/badvpn_dns/lwip/src/netif/ppp/vj.c        |  652 +++
 external/badvpn_dns/lwip/src/netif/ppp/vj.h        |  156 +
 external/badvpn_dns/lwip/src/netif/slipif.c        |  546 +++
 external/badvpn_dns/lwip/test/unit/core/test_mem.c |   73 +
 external/badvpn_dns/lwip/test/unit/core/test_mem.h |    8 +
 .../badvpn_dns/lwip/test/unit/core/test_pbuf.c     |   73 +
 .../badvpn_dns/lwip/test/unit/core/test_pbuf.h     |    8 +
 .../badvpn_dns/lwip/test/unit/dhcp/test_dhcp.c     |  916 ++++
 .../badvpn_dns/lwip/test/unit/dhcp/test_dhcp.h     |    8 +
 .../badvpn_dns/lwip/test/unit/etharp/test_etharp.c |  262 ++
 .../badvpn_dns/lwip/test/unit/etharp/test_etharp.h |    8 +
 external/badvpn_dns/lwip/test/unit/lwip_check.h    |   37 +
 .../badvpn_dns/lwip/test/unit/lwip_unittests.c     |   49 +
 external/badvpn_dns/lwip/test/unit/lwipopts.h      |   53 +
 .../badvpn_dns/lwip/test/unit/tcp/tcp_helper.c     |  303 ++
 .../badvpn_dns/lwip/test/unit/tcp/tcp_helper.h     |   52 +
 external/badvpn_dns/lwip/test/unit/tcp/test_tcp.c  |  671 +++
 external/badvpn_dns/lwip/test/unit/tcp/test_tcp.h  |    8 +
 .../badvpn_dns/lwip/test/unit/tcp/test_tcp_oos.c   |  958 ++++
 .../badvpn_dns/lwip/test/unit/tcp/test_tcp_oos.h   |    8 +
 external/badvpn_dns/lwip/test/unit/udp/test_udp.c  |   68 +
 external/badvpn_dns/lwip/test/unit/udp/test_udp.h  |    8 +
 external/badvpn_dns/misc/BRefTarget.h              |  114 +
 external/badvpn_dns/misc/Utf16Decoder.h            |  113 +
 external/badvpn_dns/misc/Utf16Encoder.h            |   67 +
 external/badvpn_dns/misc/Utf8Decoder.h             |  143 +
 external/badvpn_dns/misc/Utf8Encoder.h             |   81 +
 external/badvpn_dns/misc/arp_proto.h               |   60 +
 external/badvpn_dns/misc/array_length.h            |   35 +
 external/badvpn_dns/misc/balign.h                  |   76 +
 external/badvpn_dns/misc/balloc.h                  |  248 +
 external/badvpn_dns/misc/blimits.h                 |   60 +
 external/badvpn_dns/misc/bsize.h                   |  117 +
 external/badvpn_dns/misc/bsort.h                   |   69 +
 external/badvpn_dns/misc/bstring.h                 |  140 +
 external/badvpn_dns/misc/byteorder.h               |  196 +
 external/badvpn_dns/misc/cmdline.h                 |  181 +
 external/badvpn_dns/misc/compare.h                 |   37 +
 external/badvpn_dns/misc/concat_strings.h          |   85 +
 external/badvpn_dns/misc/cstring.h                 |  347 ++
 external/badvpn_dns/misc/dead.h                    |  134 +
 external/badvpn_dns/misc/debug.h                   |  142 +
 external/badvpn_dns/misc/debugcounter.h            |  118 +
 external/badvpn_dns/misc/debugerror.h              |   90 +
 external/badvpn_dns/misc/dhcp_proto.h              |  131 +
 external/badvpn_dns/misc/ethernet_proto.h          |   52 +
 external/badvpn_dns/misc/exparray.h                |  101 +
 external/badvpn_dns/misc/expstring.h               |  161 +
 external/badvpn_dns/misc/find_char.h               |   58 +
 external/badvpn_dns/misc/find_program.h            |  103 +
 external/badvpn_dns/misc/get_iface_info.h          |  110 +
 external/badvpn_dns/misc/grow_array.h              |  139 +
 external/badvpn_dns/misc/hashfun.h                 |   60 +
 external/badvpn_dns/misc/igmp_proto.h              |   97 +
 external/badvpn_dns/misc/ipaddr.h                  |  218 +
 external/badvpn_dns/misc/ipaddr6.h                 |  400 ++
 external/badvpn_dns/misc/ipv4_proto.h              |  145 +
 external/badvpn_dns/misc/ipv6_proto.h              |   86 +
 external/badvpn_dns/misc/loggers_string.h          |   43 +
 external/badvpn_dns/misc/loglevel.h                |   80 +
 external/badvpn_dns/misc/maxalign.h                |   53 +
 external/badvpn_dns/misc/merge.h                   |   36 +
 external/badvpn_dns/misc/minmax.h                  |   56 +
 external/badvpn_dns/misc/modadd.h                  |   59 +
 external/badvpn_dns/misc/mswsock.h                 |  229 +
 external/badvpn_dns/misc/nonblocking.h             |   51 +
 external/badvpn_dns/misc/nsskey.h                  |  118 +
 external/badvpn_dns/misc/offset.h                  |   51 +
 external/badvpn_dns/misc/open_standard_streams.h   |   54 +
 external/badvpn_dns/misc/overflow.h                |   66 +
 external/badvpn_dns/misc/packed.h                  |   51 +
 external/badvpn_dns/misc/parse_number.h            |  304 ++
 external/badvpn_dns/misc/print_macros.h            |   98 +
 external/badvpn_dns/misc/read_file.h               |   98 +
 external/badvpn_dns/misc/read_write_int.h          |  181 +
 external/badvpn_dns/misc/socks_proto.h             |  118 +
 external/badvpn_dns/misc/sslsocket.h               |   48 +
 external/badvpn_dns/misc/stdbuf_cmdline.h          |   92 +
 external/badvpn_dns/misc/strdup.h                  |   86 +
 external/badvpn_dns/misc/string_begins_with.h      |   96 +
 external/badvpn_dns/misc/substring.h               |   81 +
 external/badvpn_dns/misc/udp_proto.h               |  170 +
 external/badvpn_dns/misc/unicode_funcs.h           |  232 +
 external/badvpn_dns/misc/version.h                 |   41 +
 external/badvpn_dns/misc/write_file.h              |  104 +
 external/badvpn_dns/ncd-request/CMakeLists.txt     |    9 +
 external/badvpn_dns/ncd-request/ncd-request.c      |  224 +
 external/badvpn_dns/ncd/CMakeLists.txt             |  211 +
 external/badvpn_dns/ncd/NCDAst.c                   | 1022 ++++
 external/badvpn_dns/ncd/NCDAst.h                   |  237 +
 external/badvpn_dns/ncd/NCDBuildProgram.c          |  316 ++
 external/badvpn_dns/ncd/NCDBuildProgram.h          |   49 +
 external/badvpn_dns/ncd/NCDConfigParser.c          |  214 +
 external/badvpn_dns/ncd/NCDConfigParser.h          |   40 +
 external/badvpn_dns/ncd/NCDConfigParser_parse.y    |  718 +++
 external/badvpn_dns/ncd/NCDConfigTokenizer.c       |  321 ++
 external/badvpn_dns/ncd/NCDConfigTokenizer.h       |   64 +
 external/badvpn_dns/ncd/NCDInterpProcess.c         |  497 ++
 external/badvpn_dns/ncd/NCDInterpProcess.h         |  100 +
 external/badvpn_dns/ncd/NCDInterpProg.c            |  140 +
 external/badvpn_dns/ncd/NCDInterpProg.h            |   63 +
 external/badvpn_dns/ncd/NCDInterpProg_hash.h       |   12 +
 external/badvpn_dns/ncd/NCDInterpreter.c           | 1356 ++++++
 external/badvpn_dns/ncd/NCDInterpreter.h           |  156 +
 external/badvpn_dns/ncd/NCDMethodIndex.c           |  272 ++
 external/badvpn_dns/ncd/NCDMethodIndex.h           |  135 +
 external/badvpn_dns/ncd/NCDMethodIndex_hash.h      |   12 +
 external/badvpn_dns/ncd/NCDModule.c                |  625 +++
 external/badvpn_dns/ncd/NCDModule.h                | 1011 ++++
 external/badvpn_dns/ncd/NCDModuleIndex.c           |  372 ++
 external/badvpn_dns/ncd/NCDModuleIndex.h           |   86 +
 external/badvpn_dns/ncd/NCDModuleIndex_mhash.h     |   12 +
 external/badvpn_dns/ncd/NCDObject.c                |   40 +
 external/badvpn_dns/ncd/NCDObject.h                |  356 ++
 external/badvpn_dns/ncd/NCDPlaceholderDb.c         |  127 +
 external/badvpn_dns/ncd/NCDPlaceholderDb.h         |   95 +
 external/badvpn_dns/ncd/NCDStringIndex.c           |  262 ++
 external/badvpn_dns/ncd/NCDStringIndex.h           |   83 +
 external/badvpn_dns/ncd/NCDStringIndex_hash.h      |   13 +
 external/badvpn_dns/ncd/NCDSugar.c                 |  253 +
 external/badvpn_dns/ncd/NCDSugar.h                 |   38 +
 external/badvpn_dns/ncd/NCDVal.c                   | 2065 +++++++++
 external/badvpn_dns/ncd/NCDVal.h                   |  857 ++++
 external/badvpn_dns/ncd/NCDValCons.c               |  283 ++
 external/badvpn_dns/ncd/NCDValCons.h               |  176 +
 external/badvpn_dns/ncd/NCDValGenerator.c          |  193 +
 external/badvpn_dns/ncd/NCDValGenerator.h          |   40 +
 external/badvpn_dns/ncd/NCDValParser.c             |  225 +
 external/badvpn_dns/ncd/NCDValParser.h             |   50 +
 external/badvpn_dns/ncd/NCDValParser_parse.y       |  202 +
 external/badvpn_dns/ncd/NCDVal_maptree.h           |   15 +
 external/badvpn_dns/ncd/README                     |  386 ++
 external/badvpn_dns/ncd/emncd.c                    |  137 +
 external/badvpn_dns/ncd/emncd.html                 |  320 ++
 external/badvpn_dns/ncd/examples/dbus_start.ncd    |   82 +
 .../badvpn_dns/ncd/examples/dhcpd.conf.template    |   11 +
 .../badvpn_dns/ncd/examples/directory_updater.ncd  |   72 +
 external/badvpn_dns/ncd/examples/events.ncd        |  101 +
 .../ncd/examples/igmpproxy.conf.template           |   10 +
 .../badvpn_dns/ncd/examples/make_dhcp_config.ncd   |   27 +
 .../ncd/examples/make_igmpproxy_config.ncd         |   53 +
 external/badvpn_dns/ncd/examples/network.ncd       |  123 +
 external/badvpn_dns/ncd/examples/onoff_server.ncdi |   82 +
 .../badvpn_dns/ncd/examples/onoff_server_test.ncd  |   20 +
 external/badvpn_dns/ncd/examples/router/README     |   36 +
 .../ncd/examples/router/add-port-forwarding        |   43 +
 .../ncd/examples/router/dhcp_server.ncdi           |   60 +
 .../ncd/examples/router/list-port-forwardings      |   61 +
 external/badvpn_dns/ncd/examples/router/ncd.conf   |    6 +
 .../badvpn_dns/ncd/examples/router/network.ncdi    |  356 ++
 .../examples/router/network_control_server.ncdi    |   96 +
 .../ncd/examples/router/port_forwarding.ncdi       |  170 +
 external/badvpn_dns/ncd/examples/router/pppoe.ncdi |  296 ++
 .../ncd/examples/router/remove-port-forwarding     |   43 +
 .../badvpn_dns/ncd/examples/router/unbound.ncdi    |   42 +
 .../badvpn_dns/ncd/examples/tcp_echo_client.ncd    |   35 +
 .../badvpn_dns/ncd/examples/tcp_echo_server.ncd    |   40 +
 external/badvpn_dns/ncd/extra/BEventLock.c         |  146 +
 external/badvpn_dns/ncd/extra/BEventLock.h         |  127 +
 external/badvpn_dns/ncd/extra/NCDBProcessOpts.c    |  154 +
 external/badvpn_dns/ncd/extra/NCDBProcessOpts.h    |   54 +
 external/badvpn_dns/ncd/extra/NCDBuf.c             |  123 +
 external/badvpn_dns/ncd/extra/NCDBuf.h             |   61 +
 external/badvpn_dns/ncd/extra/NCDIfConfig.c        |  483 ++
 external/badvpn_dns/ncd/extra/NCDIfConfig.h        |   70 +
 .../badvpn_dns/ncd/extra/NCDInterfaceMonitor.c     |  446 ++
 .../badvpn_dns/ncd/extra/NCDInterfaceMonitor.h     |  160 +
 external/badvpn_dns/ncd/extra/NCDRequestClient.c   |  647 +++
 external/badvpn_dns/ncd/extra/NCDRequestClient.h   |  111 +
 external/badvpn_dns/ncd/extra/NCDRfkillMonitor.c   |  117 +
 external/badvpn_dns/ncd/extra/NCDRfkillMonitor.h   |   53 +
 external/badvpn_dns/ncd/extra/address_utils.h      |  280 ++
 external/badvpn_dns/ncd/extra/build_cmdline.c      |  111 +
 external/badvpn_dns/ncd/extra/build_cmdline.h      |   38 +
 external/badvpn_dns/ncd/extra/make_fast_names.h    |  154 +
 external/badvpn_dns/ncd/extra/value_utils.h        |  174 +
 external/badvpn_dns/ncd/include_linux_input.c      |    1 +
 external/badvpn_dns/ncd/make_name_indices.h        |  104 +
 external/badvpn_dns/ncd/modules/alias.c            |  148 +
 external/badvpn_dns/ncd/modules/arithmetic.c       |  404 ++
 external/badvpn_dns/ncd/modules/assert.c           |  105 +
 external/badvpn_dns/ncd/modules/backtrack.c        |  103 +
 external/badvpn_dns/ncd/modules/blocker.c          |  353 ++
 external/badvpn_dns/ncd/modules/buffer.c           |  619 +++
 .../badvpn_dns/ncd/modules/buffer_chunks_tree.h    |    9 +
 external/badvpn_dns/ncd/modules/call2.c            |  498 ++
 external/badvpn_dns/ncd/modules/choose.c           |  145 +
 external/badvpn_dns/ncd/modules/command_template.c |  218 +
 external/badvpn_dns/ncd/modules/command_template.h |   62 +
 external/badvpn_dns/ncd/modules/concat.c           |  189 +
 external/badvpn_dns/ncd/modules/daemon.c           |  296 ++
 external/badvpn_dns/ncd/modules/depend.c           |  452 ++
 external/badvpn_dns/ncd/modules/depend_scope.c     |  466 ++
 external/badvpn_dns/ncd/modules/dynamic_depend.c   |  548 +++
 external/badvpn_dns/ncd/modules/event_template.c   |  184 +
 external/badvpn_dns/ncd/modules/event_template.h   |   64 +
 external/badvpn_dns/ncd/modules/exit.c             |   91 +
 external/badvpn_dns/ncd/modules/explode.c          |  232 +
 external/badvpn_dns/ncd/modules/file.c             |  350 ++
 external/badvpn_dns/ncd/modules/file_open.c        |  585 +++
 external/badvpn_dns/ncd/modules/foreach.c          |  715 +++
 external/badvpn_dns/ncd/modules/from_string.c      |  125 +
 external/badvpn_dns/ncd/modules/getargs.c          |   98 +
 external/badvpn_dns/ncd/modules/getenv.c           |  153 +
 external/badvpn_dns/ncd/modules/if.c               |  103 +
 external/badvpn_dns/ncd/modules/imperative.c       |  324 ++
 external/badvpn_dns/ncd/modules/implode.c          |  155 +
 external/badvpn_dns/ncd/modules/index.c            |  164 +
 external/badvpn_dns/ncd/modules/list.c             |  871 ++++
 external/badvpn_dns/ncd/modules/load_module.c      |  313 ++
 external/badvpn_dns/ncd/modules/log.c              |  285 ++
 external/badvpn_dns/ncd/modules/logical.c          |  160 +
 external/badvpn_dns/ncd/modules/modules.h          |  210 +
 external/badvpn_dns/ncd/modules/multidepend.c      |  401 ++
 .../badvpn_dns/ncd/modules/net_backend_badvpn.c    |  281 ++
 .../badvpn_dns/ncd/modules/net_backend_rfkill.c    |  216 +
 .../ncd/modules/net_backend_waitdevice.c           |  187 +
 .../badvpn_dns/ncd/modules/net_backend_waitlink.c  |  155 +
 .../ncd/modules/net_backend_wpa_supplicant.c       |  573 +++
 external/badvpn_dns/ncd/modules/net_dns.c          |  434 ++
 external/badvpn_dns/ncd/modules/net_iptables.c     |  749 +++
 external/badvpn_dns/ncd/modules/net_ipv4_addr.c    |  148 +
 .../ncd/modules/net_ipv4_addr_in_network.c         |  173 +
 .../badvpn_dns/ncd/modules/net_ipv4_arp_probe.c    |  212 +
 external/badvpn_dns/ncd/modules/net_ipv4_dhcp.c    |  351 ++
 external/badvpn_dns/ncd/modules/net_ipv4_route.c   |  211 +
 external/badvpn_dns/ncd/modules/net_ipv6_addr.c    |  148 +
 .../ncd/modules/net_ipv6_addr_in_network.c         |  168 +
 external/badvpn_dns/ncd/modules/net_ipv6_route.c   |  213 +
 .../ncd/modules/net_ipv6_wait_dynamic_addr.c       |  201 +
 external/badvpn_dns/ncd/modules/net_up.c           |  119 +
 .../badvpn_dns/ncd/modules/net_watch_interfaces.c  |  474 ++
 external/badvpn_dns/ncd/modules/netmask.c          |  263 ++
 external/badvpn_dns/ncd/modules/ondemand.c         |  372 ++
 external/badvpn_dns/ncd/modules/parse.c            |  392 ++
 external/badvpn_dns/ncd/modules/print.c            |  207 +
 external/badvpn_dns/ncd/modules/process_manager.c  |  538 +++
 external/badvpn_dns/ncd/modules/reboot.c           |  103 +
 external/badvpn_dns/ncd/modules/ref.c              |  215 +
 external/badvpn_dns/ncd/modules/regex_match.c      |  369 ++
 external/badvpn_dns/ncd/modules/run.c              |  187 +
 external/badvpn_dns/ncd/modules/runonce.c          |  331 ++
 external/badvpn_dns/ncd/modules/sleep.c            |  178 +
 external/badvpn_dns/ncd/modules/socket.c           | 1057 +++++
 external/badvpn_dns/ncd/modules/spawn.c            |  410 ++
 external/badvpn_dns/ncd/modules/strcmp.c           |  107 +
 external/badvpn_dns/ncd/modules/substr.c           |  167 +
 external/badvpn_dns/ncd/modules/sys_evdev.c        |  348 ++
 .../badvpn_dns/ncd/modules/sys_request_client.c    |  646 +++
 .../badvpn_dns/ncd/modules/sys_request_server.c    |  792 ++++
 .../badvpn_dns/ncd/modules/sys_start_process.c     | 1266 +++++
 .../badvpn_dns/ncd/modules/sys_watch_directory.c   |  425 ++
 external/badvpn_dns/ncd/modules/sys_watch_input.c  |  455 ++
 external/badvpn_dns/ncd/modules/sys_watch_usb.c    |  421 ++
 external/badvpn_dns/ncd/modules/timer.c            |  146 +
 external/badvpn_dns/ncd/modules/to_string.c        |  116 +
 external/badvpn_dns/ncd/modules/try.c              |  302 ++
 external/badvpn_dns/ncd/modules/value.c            | 2102 +++++++++
 external/badvpn_dns/ncd/modules/value_maptree.h    |   11 +
 external/badvpn_dns/ncd/modules/valuemetic.c       |  219 +
 external/badvpn_dns/ncd/modules/var.c              |  163 +
 external/badvpn_dns/ncd/ncd.c                      |  463 ++
 external/badvpn_dns/ncd/ncd.h                      |   37 +
 external/badvpn_dns/ncd/parse_linux_input.sh       |   94 +
 external/badvpn_dns/ncd/static_strings.h           |   70 +
 external/badvpn_dns/ncd/tests/addr_in_network.ncd  |   60 +
 external/badvpn_dns/ncd/tests/alias.ncd            |   48 +
 external/badvpn_dns/ncd/tests/arithmetic.ncd       |   69 +
 external/badvpn_dns/ncd/tests/backtracking.ncd     |   31 +
 external/badvpn_dns/ncd/tests/buffer.ncd           |   54 +
 external/badvpn_dns/ncd/tests/call.ncd             |   18 +
 external/badvpn_dns/ncd/tests/concat.ncd           |   19 +
 external/badvpn_dns/ncd/tests/depend.ncd           |   64 +
 external/badvpn_dns/ncd/tests/depend_scope.ncd     |   31 +
 external/badvpn_dns/ncd/tests/escape_and_nulls.ncd |   38 +
 external/badvpn_dns/ncd/tests/explode.ncd          |   23 +
 external/badvpn_dns/ncd/tests/foreach.ncd          |   35 +
 external/badvpn_dns/ncd/tests/if.ncd               |   38 +
 external/badvpn_dns/ncd/tests/implode.ncd          |   15 +
 external/badvpn_dns/ncd/tests/include.ncd          |   16 +
 .../badvpn_dns/ncd/tests/include_included.ncdi     |    5 +
 .../badvpn_dns/ncd/tests/include_included2.ncdi    |    5 +
 external/badvpn_dns/ncd/tests/logical.ncd          |   46 +
 external/badvpn_dns/ncd/tests/multidepend.ncd      |   30 +
 external/badvpn_dns/ncd/tests/netmask.ncd          |   15 +
 external/badvpn_dns/ncd/tests/parse.ncd            |   85 +
 external/badvpn_dns/ncd/tests/process_manager.ncd  |  112 +
 external/badvpn_dns/ncd/tests/regex.ncd            |   48 +
 external/badvpn_dns/ncd/tests/run_tests            |   38 +
 external/badvpn_dns/ncd/tests/strings.ncd          |   47 +
 external/badvpn_dns/ncd/tests/substr.ncd           |   37 +
 external/badvpn_dns/ncd/tests/turing.ncd           |  138 +
 external/badvpn_dns/ncd/tests/value.ncd            |  258 ++
 external/badvpn_dns/ncd/tests/value_substr.ncd     |   25 +
 external/badvpn_dns/nspr_support/BSSLConnection.c  | 1024 ++++
 external/badvpn_dns/nspr_support/BSSLConnection.h  |  116 +
 external/badvpn_dns/nspr_support/CMakeLists.txt    |    5 +
 external/badvpn_dns/nspr_support/DummyPRFileDesc.c |  176 +
 external/badvpn_dns/nspr_support/DummyPRFileDesc.h |   61 +
 external/badvpn_dns/predicate/BPredicate.c         |  284 ++
 external/badvpn_dns/predicate/BPredicate.h         |  177 +
 external/badvpn_dns/predicate/BPredicate.l         |   83 +
 external/badvpn_dns/predicate/BPredicate.y         |  345 ++
 .../badvpn_dns/predicate/BPredicate_internal.h     |  154 +
 external/badvpn_dns/predicate/BPredicate_parser.h  |   47 +
 external/badvpn_dns/predicate/CMakeLists.txt       |    6 +
 .../badvpn_dns/predicate/LexMemoryBufferInput.h    |   86 +
 external/badvpn_dns/protocol/addr.bproto           |   11 +
 external/badvpn_dns/protocol/addr.h                |  207 +
 external/badvpn_dns/protocol/dataproto.h           |   91 +
 external/badvpn_dns/protocol/fragmentproto.h       |  100 +
 external/badvpn_dns/protocol/msgproto.bproto       |   43 +
 external/badvpn_dns/protocol/msgproto.h            |   76 +
 external/badvpn_dns/protocol/packetproto.h         |   68 +
 external/badvpn_dns/protocol/requestproto.h        |   50 +
 external/badvpn_dns/protocol/scproto.h             |  266 ++
 external/badvpn_dns/protocol/spproto.h             |  195 +
 external/badvpn_dns/protocol/udpgw_proto.h         |   84 +
 external/badvpn_dns/random/BRandom2.c              |   90 +
 external/badvpn_dns/random/BRandom2.h              |   50 +
 external/badvpn_dns/random/CMakeLists.txt          |    1 +
 external/badvpn_dns/scripts/cmake                  |    8 +
 external/badvpn_dns/scripts/copy_nss               |   23 +
 external/badvpn_dns/scripts/toolchain.cmake        |    6 +
 external/badvpn_dns/security/BEncryption.c         |  240 +
 external/badvpn_dns/security/BEncryption.h         |  175 +
 external/badvpn_dns/security/BHash.c               |   69 +
 external/badvpn_dns/security/BHash.h               |   80 +
 external/badvpn_dns/security/BRandom.c             |   42 +
 external/badvpn_dns/security/BRandom.h             |   49 +
 external/badvpn_dns/security/BSecurity.c           |  149 +
 external/badvpn_dns/security/BSecurity.h           |   60 +
 external/badvpn_dns/security/CMakeLists.txt        |   10 +
 external/badvpn_dns/security/OTPCalculator.c       |  118 +
 external/badvpn_dns/security/OTPCalculator.h       |   96 +
 external/badvpn_dns/security/OTPChecker.c          |  297 ++
 external/badvpn_dns/security/OTPChecker.h          |  148 +
 external/badvpn_dns/security/OTPGenerator.c        |  159 +
 external/badvpn_dns/security/OTPGenerator.h        |  134 +
 external/badvpn_dns/server/CMakeLists.txt          |   12 +
 external/badvpn_dns/server/badvpn-server.8         |  190 +
 external/badvpn_dns/server/server.c                | 2394 ++++++++++
 external/badvpn_dns/server/server.h                |  186 +
 .../badvpn_dns/server_connection/CMakeLists.txt    |    5 +
 .../server_connection/SCKeepaliveSource.c          |   69 +
 .../server_connection/SCKeepaliveSource.h          |   72 +
 .../server_connection/ServerConnection.c           |  669 +++
 .../server_connection/ServerConnection.h           |  289 ++
 external/badvpn_dns/socksclient/BSocksClient.c     |  608 +++
 external/badvpn_dns/socksclient/BSocksClient.h     |  147 +
 external/badvpn_dns/socksclient/CMakeLists.txt     |    1 +
 external/badvpn_dns/stringmap/BStringMap.c         |  198 +
 external/badvpn_dns/stringmap/BStringMap.h         |   57 +
 external/badvpn_dns/stringmap/CMakeLists.txt       |    1 +
 external/badvpn_dns/structure/BAVL.h               |  797 ++++
 external/badvpn_dns/structure/CAvl.h               |   36 +
 external/badvpn_dns/structure/CAvl_decl.h          |   77 +
 external/badvpn_dns/structure/CAvl_footer.h        |  113 +
 external/badvpn_dns/structure/CAvl_header.h        |  141 +
 external/badvpn_dns/structure/CAvl_impl.h          |  949 ++++
 external/badvpn_dns/structure/CHash.h              |   39 +
 external/badvpn_dns/structure/CHash_decl.h         |   59 +
 external/badvpn_dns/structure/CHash_footer.h       |   74 +
 external/badvpn_dns/structure/CHash_header.h       |   78 +
 external/badvpn_dns/structure/CHash_impl.h         |  312 ++
 external/badvpn_dns/structure/ChunkBuffer2.h       |  317 ++
 external/badvpn_dns/structure/IndexedList.h        |  225 +
 external/badvpn_dns/structure/IndexedList_tree.h   |   15 +
 external/badvpn_dns/structure/LinkedList0.h        |  202 +
 external/badvpn_dns/structure/LinkedList1.h        |  275 ++
 external/badvpn_dns/structure/LinkedList3.h        |  362 ++
 external/badvpn_dns/structure/SAvl.h               |   40 +
 external/badvpn_dns/structure/SAvl_decl.h          |   73 +
 external/badvpn_dns/structure/SAvl_footer.h        |   89 +
 external/badvpn_dns/structure/SAvl_header.h        |   93 +
 external/badvpn_dns/structure/SAvl_impl.h          |  164 +
 external/badvpn_dns/structure/SAvl_tree.h          |   18 +
 external/badvpn_dns/structure/SLinkedList.h        |   38 +
 external/badvpn_dns/structure/SLinkedList_decl.h   |   67 +
 external/badvpn_dns/structure/SLinkedList_footer.h |   57 +
 external/badvpn_dns/structure/SLinkedList_header.h |   62 +
 external/badvpn_dns/structure/SLinkedList_impl.h   |  182 +
 external/badvpn_dns/system/BAddr.h                 |  808 ++++
 external/badvpn_dns/system/BConnection.h           |  369 ++
 external/badvpn_dns/system/BConnectionGeneric.h    |  144 +
 external/badvpn_dns/system/BConnection_unix.c      | 1057 +++++
 external/badvpn_dns/system/BConnection_unix.h      |   87 +
 external/badvpn_dns/system/BConnection_win.c       |  875 ++++
 external/badvpn_dns/system/BConnection_win.h       |  101 +
 external/badvpn_dns/system/BDatagram.h             |  209 +
 external/badvpn_dns/system/BDatagram_unix.c        |  855 ++++
 external/badvpn_dns/system/BDatagram_unix.h        |   71 +
 external/badvpn_dns/system/BDatagram_win.c         |  755 +++
 external/badvpn_dns/system/BDatagram_win.h         |   99 +
 external/badvpn_dns/system/BInputProcess.c         |  211 +
 external/badvpn_dns/system/BInputProcess.h         |   65 +
 external/badvpn_dns/system/BLockReactor.c          |  131 +
 external/badvpn_dns/system/BLockReactor.h          |   58 +
 external/badvpn_dns/system/BNetwork.c              |   99 +
 external/badvpn_dns/system/BNetwork.h              |   36 +
 external/badvpn_dns/system/BProcess.c              |  400 ++
 external/badvpn_dns/system/BProcess.h              |  200 +
 external/badvpn_dns/system/BReactor.h              |   11 +
 external/badvpn_dns/system/BReactor_badvpn.c       | 1430 ++++++
 external/badvpn_dns/system/BReactor_badvpn.h       |  572 +++
 .../badvpn_dns/system/BReactor_badvpn_timerstree.h |   13 +
 external/badvpn_dns/system/BReactor_emscripten.c   |  176 +
 external/badvpn_dns/system/BReactor_emscripten.h   |   87 +
 external/badvpn_dns/system/BReactor_glib.c         |  524 +++
 external/badvpn_dns/system/BReactor_glib.h         |  148 +
 external/badvpn_dns/system/BSignal.c               |  188 +
 external/badvpn_dns/system/BSignal.h               |   64 +
 external/badvpn_dns/system/BThreadSignal.c         |  136 +
 external/badvpn_dns/system/BThreadSignal.h         |   53 +
 external/badvpn_dns/system/BTime.c                 |   38 +
 external/badvpn_dns/system/BTime.h                 |  163 +
 external/badvpn_dns/system/BUnixSignal.c           |  406 ++
 external/badvpn_dns/system/BUnixSignal.h           |  132 +
 external/badvpn_dns/system/CMakeLists.txt          |   44 +
 external/badvpn_dns/tests/CMakeLists.txt           |    8 +
 external/badvpn_dns/tests/bproto_test.bproto       |    9 +
 external/badvpn_dns/tests/bproto_test.c            |   76 +
 external/badvpn_dns/tests/chunkbuffer2_test.c      |   86 +
 external/badvpn_dns/tests/threadwork_test.c        |   87 +
 external/badvpn_dns/threadwork/BThreadWork.c       |  451 ++
 external/badvpn_dns/threadwork/BThreadWork.h       |  171 +
 external/badvpn_dns/threadwork/CMakeLists.txt      |    6 +
 external/badvpn_dns/tun2socks/CMakeLists.txt       |   15 +
 external/badvpn_dns/tun2socks/SocksUdpGwClient.c   |  228 +
 external/badvpn_dns/tun2socks/SocksUdpGwClient.h   |   64 +
 external/badvpn_dns/tun2socks/badvpn-tun2socks.8   |  126 +
 external/badvpn_dns/tun2socks/tun2socks.c          | 2138 +++++++++
 external/badvpn_dns/tun2socks/tun2socks.h          |   46 +
 external/badvpn_dns/tunctl/CMakeLists.txt          |    6 +
 external/badvpn_dns/tunctl/tunctl.c                |  352 ++
 external/badvpn_dns/tuntap/BTap.c                  |  631 +++
 external/badvpn_dns/tuntap/BTap.h                  |  199 +
 external/badvpn_dns/tuntap/CMakeLists.txt          |   10 +
 external/badvpn_dns/tuntap/tapwin32-funcs.c        |  227 +
 external/badvpn_dns/tuntap/tapwin32-funcs.h        |   42 +
 external/badvpn_dns/tuntap/wintap-common.h         |   39 +
 external/badvpn_dns/udevmonitor/CMakeLists.txt     |    7 +
 external/badvpn_dns/udevmonitor/NCDUdevCache.c     |  417 ++
 external/badvpn_dns/udevmonitor/NCDUdevCache.h     |   66 +
 external/badvpn_dns/udevmonitor/NCDUdevManager.c   |  547 +++
 external/badvpn_dns/udevmonitor/NCDUdevManager.h   |   84 +
 external/badvpn_dns/udevmonitor/NCDUdevMonitor.c   |  250 +
 external/badvpn_dns/udevmonitor/NCDUdevMonitor.h   |   71 +
 .../badvpn_dns/udevmonitor/NCDUdevMonitorParser.c  |  358 ++
 .../badvpn_dns/udevmonitor/NCDUdevMonitorParser.h  |   76 +
 external/badvpn_dns/udpgw/CMakeLists.txt           |    9 +
 external/badvpn_dns/udpgw/udpgw.c                  | 1473 ++++++
 external/badvpn_dns/udpgw/udpgw.h                  |   52 +
 external/badvpn_dns/udpgw_client/CMakeLists.txt    |    1 +
 external/badvpn_dns/udpgw_client/UdpGwClient.c     |  597 +++
 external/badvpn_dns/udpgw_client/UdpGwClient.h     |  118 +
 jni/Android.mk                                     |    2 +-
 972 files changed, 227044 insertions(+), 1 deletion(-)

diff --git a/external/badvpn_dns/Android.mk b/external/badvpn_dns/Android.mk
new file mode 100644
index 0000000..3829065
--- /dev/null
+++ b/external/badvpn_dns/Android.mk
@@ -0,0 +1,75 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := tun2socks
+
+LOCAL_CFLAGS := -std=gnu99
+LOCAL_CFLAGS += -DBADVPN_THREAD_SAFE=0 -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE
+LOCAL_CFLAGS += -DBADVPN_USE_SELFPIPE -DBADVPN_USE_EPOLL
+LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN
+LOCAL_CFLAGS += -DPSIPHON
+
+LOCAL_C_INCLUDES:= \
+        $(LOCAL_PATH) \
+        $(LOCAL_PATH)/lwip/src/include/ipv4 \
+        $(LOCAL_PATH)/lwip/src/include/ipv6 \
+        $(LOCAL_PATH)/lwip/src/include \
+        $(LOCAL_PATH)/lwip/custom
+
+LOCAL_SRC_FILES := \
+        base/BLog_syslog.c \
+        system/BReactor_badvpn.c \
+        system/BSignal.c \
+        system/BConnection_unix.c \
+        system/BTime.c \
+        system/BUnixSignal.c \
+        system/BNetwork.c \
+        flow/StreamRecvInterface.c \
+        flow/PacketRecvInterface.c \
+        flow/PacketPassInterface.c \
+        flow/StreamPassInterface.c \
+        flow/SinglePacketBuffer.c \
+        flow/BufferWriter.c \
+        flow/PacketBuffer.c \
+        flow/PacketStreamSender.c \
+        flow/PacketPassConnector.c \
+        flow/PacketProtoFlow.c \
+        flow/PacketPassFairQueue.c \
+        flow/PacketProtoEncoder.c \
+        flow/PacketProtoDecoder.c \
+        socksclient/BSocksClient.c \
+        tuntap/BTap.c \
+        lwip/src/core/timers.c \
+        lwip/src/core/udp.c \
+        lwip/src/core/memp.c \
+        lwip/src/core/init.c \
+        lwip/src/core/pbuf.c \
+        lwip/src/core/tcp.c \
+        lwip/src/core/tcp_out.c \
+        lwip/src/core/netif.c \
+        lwip/src/core/def.c \
+        lwip/src/core/mem.c \
+        lwip/src/core/tcp_in.c \
+        lwip/src/core/stats.c \
+        lwip/src/core/inet_chksum.c \
+        lwip/src/core/ipv4/icmp.c \
+        lwip/src/core/ipv4/ip4.c \
+        lwip/src/core/ipv4/ip4_addr.c \
+        lwip/src/core/ipv4/ip_frag.c \
+        lwip/src/core/ipv6/ip6.c \
+        lwip/src/core/ipv6/nd6.c \
+        lwip/src/core/ipv6/icmp6.c \
+        lwip/src/core/ipv6/ip6_addr.c \
+        lwip/src/core/ipv6/ip6_frag.c \
+        lwip/custom/sys.c \
+        tun2socks/tun2socks.c \
+        base/DebugObject.c \
+        base/BLog.c \
+        base/BPending.c \
+        flowextra/PacketPassInactivityMonitor.c \
+        tun2socks/SocksUdpGwClient.c \
+        udpgw_client/UdpGwClient.c 
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/external/badvpn_dns/CMakeLists.txt b/external/badvpn_dns/CMakeLists.txt
new file mode 100644
index 0000000..ebdc0d7
--- /dev/null
+++ b/external/badvpn_dns/CMakeLists.txt
@@ -0,0 +1,408 @@
+cmake_minimum_required(VERSION 2.8)
+project(BADVPN C)
+
+set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
+
+include(TestBigEndian)
+include(CheckIncludeFiles)
+include(CheckSymbolExists)
+include(CheckTypeSize)
+
+set(BUILD_COMPONENTS)
+
+macro (build_switch name text default)
+    if (BUILD_NOTHING_BY_DEFAULT)
+        option(BUILD_${name} "${text}" OFF)
+    else ()
+        option(BUILD_${name} "${text}" "${default}")
+    endif ()
+    list(APPEND BUILD_COMPONENTS "${name}")
+endmacro ()
+
+# detect Emscripten
+if (CMAKE_C_COMPILER MATCHES "/emcc$")
+    set(EMSCRIPTEN ON)
+else ()
+    set(EMSCRIPTEN OFF)
+endif ()
+
+if (EMSCRIPTEN)
+    set(ON_IF_NOT_EMSCRIPTEN OFF)
+else ()
+    set(ON_IF_NOT_EMSCRIPTEN ON)
+endif()
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT EMSCRIPTEN)
+    set(ON_IF_LINUX ON)
+else ()
+    set(ON_IF_LINUX OFF)
+endif()
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR EMSCRIPTEN)
+    set(ON_IF_LINUX_OR_EMSCRIPTEN ON)
+else ()
+    set(ON_IF_LINUX_OR_EMSCRIPTEN OFF)
+endif ()
+
+# define build defaults
+build_switch(EXAMPLES "build example programs" ON)
+build_switch(TESTS "build some other example programs" ON)
+build_switch(SERVER "build badvpn-server" ${ON_IF_NOT_EMSCRIPTEN})
+build_switch(CLIENT "build badvpn-client" ${ON_IF_NOT_EMSCRIPTEN})
+build_switch(FLOODER "build badvpn-flooder" ${ON_IF_NOT_EMSCRIPTEN})
+build_switch(TUN2SOCKS "build badvpn-tun2socks" ${ON_IF_NOT_EMSCRIPTEN})
+build_switch(UDPGW "build badvpn-udpgw" ${ON_IF_NOT_EMSCRIPTEN})
+build_switch(NCD "build badvpn-ncd" ${ON_IF_LINUX_OR_EMSCRIPTEN})
+build_switch(TUNCTL "build badvpn-tunctl" ${ON_IF_LINUX})
+build_switch(DOSTEST "build dostest-server and dostest-attacker" OFF)
+
+if (BUILD_NCD AND NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux"))
+    message(FATAL_ERROR "NCD is only available on Linux")
+endif ()
+
+if (BUILD_CLIENT OR BUILD_SERVER)
+    find_package(OpenSSL REQUIRED)
+    set(LIBCRYPTO_INCLUDE_DIRS "${OpenSSL_INCLUDE_DIRS}")
+    set(LIBCRYPTO_LIBRARY_DIRS "${OpenSSL_LIBRARY_DIRS}")
+    set(LIBCRYPTO_LIBRARIES "${OpenSSL_LIBRARIES}")
+endif ()
+
+if (BUILD_SERVER OR BUILD_CLIENT OR BUILD_FLOODER)
+    find_package(NSPR REQUIRED)
+    find_package(NSS REQUIRED)
+endif ()
+
+# choose reactor
+if (DEFINED BREACTOR_BACKEND)
+    if (NOT (BREACTOR_BACKEND STREQUAL "badvpn" OR BREACTOR_BACKEND STREQUAL "glib"))
+        message(FATAL_ERROR "unknown reactor backend specified")
+    endif ()
+else ()
+    if (EMSCRIPTEN)
+        set(BREACTOR_BACKEND "emscripten")
+    else ()
+        set(BREACTOR_BACKEND "badvpn")
+    endif ()
+endif ()
+
+if (BREACTOR_BACKEND STREQUAL "badvpn")
+    add_definitions(-DBADVPN_BREACTOR_BADVPN)
+elseif (BREACTOR_BACKEND STREQUAL "glib")
+    if (NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux"))
+        message(FATAL_ERROR "GLib reactor backend is only available on Linux")
+    endif ()
+    find_package(GLIB2 REQUIRED)
+    add_definitions(-DBADVPN_BREACTOR_GLIB)
+elseif (BREACTOR_BACKEND STREQUAL "emscripten")
+    add_definitions(-DBADVPN_BREACTOR_EMSCRIPTEN)
+endif ()
+
+include_directories(
+    ${CMAKE_CURRENT_SOURCE_DIR}
+    ${LIBCRYPTO_INCLUDE_DIRS}
+    ${NSPR_INCLUDE_DIRS}
+    ${NSS_INCLUDE_DIRS}
+    ${GLIB2_INCLUDE_DIR}
+    lwip/custom
+    lwip/src/include
+    lwip/src/include/ipv4
+    lwip/src/include/ipv6
+)
+
+link_directories(
+    ${LIBCRYPTO_LIBRARY_DIRS}
+    ${NSPR_LIBRARY_DIRS}
+    ${NSS_LIBRARY_DIRS}
+)
+
+test_big_endian(BIG_ENDIAN)
+
+check_type_size(int INT_SIZE)
+if (NOT (INT_SIZE GREATER "3"))
+    message(FATAL_ERROR "int must be at least 32 bits")
+endif ()
+
+check_type_size(size_t SIZE_SIZE)
+if (NOT (SIZE_SIZE GREATER INT_SIZE OR SIZE_SIZE EQUAL INT_SIZE))
+    message(FATAL_ERROR "size_t must be greater or equal than int")
+endif ()
+
+if (MSVC)
+    add_definitions(/TP -D_CRT_SECURE_NO_WARNINGS /wd4065 /wd4018 /wd4533 /wd4244 /wd4102)
+else ()
+    add_definitions(-std=gnu99 -Wall -Wno-unused-value -Wno-parentheses -Wno-switch -Wredundant-decls)
+
+    if (NOT CMAKE_C_COMPILER_ID STREQUAL "PathScale")
+        add_definitions(-Werror=implicit-function-declaration -Wno-switch-enum -Wno-unused-function
+                        -Wstrict-aliasing)
+    endif ()
+    
+    if (CMAKE_C_COMPILER_ID MATCHES "^Clang")
+        add_definitions(-Wno-initializer-overrides -Wno-tautological-constant-out-of-range-compare)
+    endif ()
+endif ()
+
+# platform-specific stuff
+if (WIN32)
+    add_definitions(-DBADVPN_USE_WINAPI -D_WIN32_WINNT=0x600 -DWIN32_LEAN_AND_MEAN)
+    add_definitions(-DBADVPN_THREAD_SAFE=0)
+
+    set(CMAKE_REQUIRED_DEFINITIONS "-D_WIN32_WINNT=0x600")
+    check_symbol_exists(WSAID_WSASENDMSG "winsock2.h;mswsock.h" HAVE_MSW_1)
+    check_symbol_exists(WSAID_WSARECVMSG "winsock2.h;mswsock.h" HAVE_MSW_2)
+    check_symbol_exists(WSAID_ACCEPTEX "winsock2.h;mswsock.h" HAVE_MSW_3)
+    check_symbol_exists(WSAID_GETACCEPTEXSOCKADDRS "winsock2.h;mswsock.h" HAVE_MSW_4)
+    check_symbol_exists(WSAID_CONNECTEX "winsock2.h;mswsock.h" HAVE_MSW_5)
+    set(CMAKE_REQUIRED_DEFINITIONS "")
+    if (NOT (HAVE_MSW_1 AND HAVE_MSW_2 AND HAVE_MSW_3 AND HAVE_MSW_4 AND HAVE_MSW_5))
+        add_definitions(-DBADVPN_USE_SHIPPED_MSWSOCK)
+        check_type_size(WSAMSG HAVE_WSAMSG)
+        if (NOT HAVE_WSAMSG)
+            add_definitions(-DBADVPN_SHIPPED_MSWSOCK_DECLARE_WSAMSG)
+        endif ()
+    endif ()
+else ()
+    set(BADVPN_THREADWORK_USE_PTHREAD 1)
+    add_definitions(-DBADVPN_THREADWORK_USE_PTHREAD)
+    add_definitions(-DBADVPN_THREAD_SAFE=1)
+
+    link_libraries(rt)
+
+    if (EMSCRIPTEN)
+        add_definitions(-DBADVPN_EMSCRIPTEN)
+        add_definitions(-DBADVPN_NO_PROCESS -DBADVPN_NO_UDEV -DBADVPN_NO_RANDOM)
+    elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+        add_definitions(-DBADVPN_LINUX)
+
+        check_include_files(sys/signalfd.h HAVE_SYS_SIGNALFD_H)
+        if (HAVE_SYS_SIGNALFD_H)
+            add_definitions(-DBADVPN_USE_SIGNALFD)
+        else ()
+            add_definitions(-DBADVPN_USE_SELFPIPE)
+        endif ()
+
+        check_include_files(sys/epoll.h HAVE_SYS_EPOLL_H)
+        if (HAVE_SYS_EPOLL_H)
+            add_definitions(-DBADVPN_USE_EPOLL)
+        else ()
+            add_definitions(-DBADVPN_USE_POLL)
+        endif ()
+
+        check_include_files(linux/rfkill.h HAVE_LINUX_RFKILL_H)
+        if (HAVE_LINUX_RFKILL_H)
+            add_definitions(-DBADVPN_USE_LINUX_RFKILL)
+            set(BADVPN_USE_LINUX_RFKILL 1)
+        endif ()
+
+        check_include_files(linux/input.h HAVE_LINUX_INPUT_H)
+        if (HAVE_LINUX_INPUT_H)
+            add_definitions(-DBADVPN_USE_LINUX_INPUT)
+            set(BADVPN_USE_LINUX_INPUT 1)
+        endif ()
+
+        check_include_files(sys/inotify.h HAVE_SYS_INOTIFY_H)
+        if (HAVE_SYS_INOTIFY_H)
+            add_definitions(-DBADVPN_USE_INOTIFY)
+            set(BADVPN_USE_INOTIFY 1)
+        endif ()
+    elseif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+        add_definitions(-DBADVPN_FREEBSD)
+
+        check_symbol_exists(kqueue "sys/types.h;sys/event.h;sys/time.h" HAVE_KQUEUE)
+        if (NOT HAVE_KQUEUE)
+            message(FATAL_ERROR "kqueue is required")
+        endif ()
+        add_definitions(-DBADVPN_USE_KEVENT)
+    endif ()
+
+    if (NOT DEFINED BADVPN_WITHOUT_CRYPTODEV)
+        check_include_files(crypto/cryptodev.h HAVE_CRYPTO_CRYPTODEV_H)
+        if (HAVE_CRYPTO_CRYPTODEV_H)
+            add_definitions(-DBADVPN_USE_CRYPTODEV)
+        elseif (DEFINED BADVPN_WITH_CRYPTODEV)
+            message(FATAL_ERROR "crypto/cryptodev.h not found")
+        endif ()
+    endif ()
+endif ()
+
+# check for syslog
+check_include_files(syslog.h HAVE_SYSLOG_H)
+if (HAVE_SYSLOG_H)
+    add_definitions(-DBADVPN_USE_SYSLOG)
+endif ()
+
+# add preprocessor definitions
+if (BIG_ENDIAN)
+    add_definitions(-DBADVPN_BIG_ENDIAN)
+else ()
+    add_definitions(-DBADVPN_LITTLE_ENDIAN)
+endif ()
+
+# install man pages
+install(
+    FILES badvpn.7
+    DESTINATION share/man/man7
+)
+
+# reset variables indicating whether we're building various libraries,
+# and set them in the respective CMakeLists files. This is used to disable
+# building examples and tests which require libraries that are not available.
+set(BUILDING_SECURITY 0)
+set(BUILDING_DHCPCLIENT 0)
+set(BUILDING_ARPPROBE 0)
+set(BUILDING_BKIO 0)
+set(BUILDING_PREDICATE 0)
+set(BUILDING_UDEVMONITOR 0)
+set(BUILDING_THREADWORK 0)
+set(BUILDING_RANDOM 0)
+
+# Used to register an internal library.
+# This will also add a library with the -plugin suffix, which is useful
+# for use by dynamic libraries (e.g. NCD modules):
+# - If BUILD_SHARED_LIBS is off (default), the libraries ${LIB_NAME} and ${LIB_NAME}-plugin
+#   are built separately. Both are static libraries but the -plugin variant is build as position
+#   independent code, so it can be (statically) linked into dynamic libraries.
+# - If BUILD_SHARED_LIBS is on, only ${LIB_NAME} is built, as a shared library.
+#   The ${LIB_NAME}-plugin target is set up as an alias to ${LIB_NAME}.
+function(badvpn_add_library LIB_NAME LINK_BADVPN_LIBS LINK_SYS_LIBS LIB_SOURCES)
+    set(BADVPN_LIBS_EXEC)
+    set(BADVPN_LIBS_PLUGIN)
+    foreach(LIB ${LINK_BADVPN_LIBS})
+        list(APPEND BADVPN_LIBS_EXEC "${LIB}")
+        list(APPEND BADVPN_LIBS_PLUGIN "${LIB}-plugin")
+    endforeach()
+
+    add_library("${LIB_NAME}" ${LIB_SOURCES})
+    target_link_libraries("${LIB_NAME}" ${BADVPN_LIBS_EXEC} ${LINK_SYS_LIBS})
+    set_target_properties("${LIB_NAME}" PROPERTIES OUTPUT_NAME "badvpn-${LIB_NAME}")
+
+    if (BUILD_SHARED_LIBS)
+        add_library("${LIB_NAME}-plugin" ALIAS "${LIB_NAME}")
+    else ()
+        add_library("${LIB_NAME}-plugin" STATIC ${LIB_SOURCES})
+        target_link_libraries("${LIB_NAME}-plugin" ${BADVPN_LIBS_PLUGIN} ${LINK_SYS_LIBS})
+        set_target_properties("${LIB_NAME}-plugin" PROPERTIES OUTPUT_NAME "badvpn-${LIB_NAME}-plugin")
+        set_target_properties("${LIB_NAME}-plugin" PROPERTIES POSITION_INDEPENDENT_CODE YES)
+        set_target_properties("${LIB_NAME}-plugin" PROPERTIES COMPILE_FLAGS "-fvisibility=hidden -DBADVPN_PLUGIN")
+    endif()
+endfunction()
+
+# internal libraries
+add_subdirectory(base)
+add_subdirectory(system)
+add_subdirectory(flow)
+add_subdirectory(flowextra)
+if (OpenSSL_FOUND)
+    set(BUILDING_SECURITY 1)
+    add_subdirectory(security)
+endif ()
+if (NSS_FOUND)
+    add_subdirectory(nspr_support)
+endif ()
+if (BUILD_CLIENT OR BUILDING_SECURITY)
+    set(BUILDING_THREADWORK 1)
+    add_subdirectory(threadwork)
+endif ()
+if (BUILD_CLIENT OR BUILD_TUN2SOCKS)
+    add_subdirectory(tuntap)
+endif ()
+if (BUILD_SERVER)
+    set(BUILDING_PREDICATE 1)
+    add_subdirectory(predicate)
+endif ()
+if (BUILD_CLIENT OR BUILD_FLOODER)
+    add_subdirectory(server_connection)
+endif ()
+if (BUILD_NCD AND NOT EMSCRIPTEN)
+    set(BUILDING_DHCPCLIENT 1)
+    set(BUILDING_ARPPROBE 1)
+    set(BUILDING_UDEVMONITOR 1)
+    set(BUILDING_RANDOM 1)
+    add_subdirectory(stringmap)
+    add_subdirectory(udevmonitor)
+    add_subdirectory(dhcpclient)
+    add_subdirectory(arpprobe)
+    add_subdirectory(random)
+endif ()
+if (BUILD_TUN2SOCKS)
+    add_subdirectory(socksclient)
+    add_subdirectory(udpgw_client)
+    add_subdirectory(lwip)
+endif ()
+if (BUILD_TUNCTL)
+    add_subdirectory(tunctl)
+endif ()
+
+# example programs
+if (BUILD_EXAMPLES)
+    add_subdirectory(examples)
+endif ()
+
+# tests
+if (BUILD_TESTS)
+    add_subdirectory(tests)
+endif ()
+
+# server
+if (BUILD_SERVER)
+    add_subdirectory(server)
+endif ()
+
+# client
+if (BUILD_CLIENT)
+    add_subdirectory(client)
+endif ()
+
+# flooder
+if (BUILD_FLOODER)
+    add_subdirectory(flooder)
+endif ()
+
+# tun2socks
+if (BUILD_TUN2SOCKS)
+    add_subdirectory(tun2socks)
+endif ()
+
+# udpgw
+if (BUILD_UDPGW)
+    add_subdirectory(udpgw)
+endif ()
+
+# ncd
+if (BUILD_NCD)
+    add_subdirectory(ncd)
+    if (NOT EMSCRIPTEN)
+        add_subdirectory(ncd-request)
+    endif ()
+endif ()
+
+# dostest
+if (BUILD_DOSTEST)
+    add_subdirectory(dostest)
+endif ()
+
+message(STATUS "Building components:")
+
+# print what we're building and what not
+foreach (name ${BUILD_COMPONENTS})
+    # to lower name
+    string(TOLOWER "${name}" name_withspaces)
+
+    # append spaces to name
+    #while (TRUE)
+    #    string(LENGTH "${name_withspaces}" length)
+    #    if (NOT (length LESS 12))
+    #        break()
+    #    endif ()
+    #    set(name_withspaces "${name_withspaces} ")
+    #endwhile ()
+    
+    # determine if we're building
+    if (BUILD_${name})
+        set(building "yes")
+    else ()
+        set(building "no")
+    endif ()
+    
+    message(STATUS "    ${name_withspaces} ${building}")
+endforeach ()
diff --git a/external/badvpn_dns/COPYING b/external/badvpn_dns/COPYING
new file mode 100644
index 0000000..f973347
--- /dev/null
+++ b/external/badvpn_dns/COPYING
@@ -0,0 +1,24 @@
+Copyright (c) 2009, Ambroz Bizjak <ambrop7 at gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the author nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/external/badvpn_dns/ChangeLog b/external/badvpn_dns/ChangeLog
new file mode 100644
index 0000000..4c4b96b
--- /dev/null
+++ b/external/badvpn_dns/ChangeLog
@@ -0,0 +1,216 @@
+Version 1.999.129:
+
+- ncd: modules: file_open: Fix typo in assertion.
+
+- server: Fix bug forgetting to call BSSLConnection_ReleaseBuffers(). Unless threads are enabled, this is an assert failure if NDEBUG is not defined an a non-issue otherwise.
+
+- ncd: Look for various programs in PATH instead of hardcoded paths.
+
+- Add compile-udpgw.sh.
+
+- ncd: modules: net_dns: Implement net.dns.resolvconf() forspecification of arbitrary resolv.conf lines
+
+Version 1.999.128:
+
+- tun2socks: add option --append-source-to-username to give the SOCKS server the source IP of the connection
+
+- tun2socks: IPv6 support, and updated to newer version of lwIP
+
+- tun2socks: fix some bugs/crashes
+
+- tun2socks, udpgw: transparent DNS forwarding, though no Windows support on udpgw side (contributed by Kerem Hadimli)
+
+- NCD: preliminary support for dynamically loading commands
+
+Version 1.999.127:
+
+- client, server: implement experimental support for performing SSL operations in worker threads. Currently it's rather inefficient.
+
+- NCD: modules: value: implement value::append() for appending to a list
+
+- NCD: modules: net_iptables: add single-argument form of append and insert commands, allowing for generic use
+
+- NCD: modules: net_iptables: implement net.iptables.insert() and net.ebtables.insert()
+
+- NCD: modules: sys_start_process: implement options, including username, term_on_deinit and deinit_kill_time
+
+- NCD: modules: sys_request_server: implement _caller in request handler
+
+- NCD: modules: add getenv()
+
+- NCD: modules: daemon: implement options, including username option
+
+- NCD: modules: runonce: add new options format with a map, implement username option
+
+- NCD: modules: add buffer(), which exposes a buffer with efficient appending and removing from the beginning.
+
+- NCD: add a new internal string representation called ComposedString. This allows modules to expose the concatenation of multiple memroy buffers as a single string value, efficiently.
+
+- fix many, hopefully all, strict aliasing violations. In particular, this fixes a bug where the DHCP client integrated into NCD won't work correctly, subject to optimization flags.
+
+- NCD: modules: sleep: interpret empty string as no sleeping, add sleep(ms_start) with one argument
+
+- NCD: modules: add log(), log_r() and log_fr() commands for logging via the BLog system
+
+Version 1.999.126:
+
+- NCD: modules: sleep: interpret empty string time as no sleeping, add sleep(ms_start) with one argument
+
+- NCD: modules: add log module for message logging using the BLog system
+
+- NCD: implement the "include" and "include_guard" directives, which allow separating reusable code into files
+
+- NCD: modules: call2: implement call_with_caller_target(), which makes it easier to write reusable code that calls back user-provided code
+
+- NCD: modules: call2: remove call2_if(), call2_ifelse(), embcall2(), embcall2_if(), embcall2_ifelse()
+
+- NCD: modules: add sys.start_process(), which implements starting and controlling external processes and reading/writing their stdout/stdin
+
+- tun2socks: implement SOCKS password authentication
+
+- NCD: track the depth of values and limit the maximum depth. This avoids stack overflow with very deeply nested values.
+
+- NCD: modules: add substr()
+
+- NCD: process_manager: add 2-argument start() method which doesn't take a process identifier
+
+- NCD: process_manager: allow process identifiers to be any value not just strings
+
+- NCD: multidepend, depend_scope: fix immediate effect order when a depend finishes backtracking
+
+- NCD: add depend_scope module to do exactly what the multidepend module does, but with separate non-global dependency name scopes
+
+- NCD: multidepend: allow dependency names to be any value not just strings
+
+- NCD: implement value::insert(what) for appending to a list
+
+- NCD: change the format of addresses in sys.request_server() and sys.request_client() to be the same as in the socket module
+
+- NCD: add socket module (sys.connect() and sys.listen())
+
+- NCD: fix bug where duplicate template/process names would not be detected and weird behaviour would result
+
+- NCD: add backtrack_point() for simple backtracking
+
+- NCD: add file_open() for more complete file I/O
+
+- NCD: implement parse_ipv6_addr() and parse_ipv6_cidr_addr()
+
+- NCD: port to Emscripten/Javascript, for the in-browser demo
+
+- NCD: many performance and memory usage improvements
+
+- NCD: add assert_false()
+
+- NCD: don't link to OpenSSL to for random number generator. Use /dev/urandom instead to generate XIDs for DHCP.
+
+- NCD: deprecate ip_in_network() and instead add net.ipv{4,6}.addr_in_network(), net.ipv{4,6}.ifnot_addr_in_network()
+
+- NCD: implement some IPv6 modules: net.ipv6.addr(), net.ipv6.route()
+
+- NCD: support CIDR style addr/prefix addresses in various modules
+
+- NCD: recognize Elif and Else with capital first letter to be consistent with other reserved keywords
+
+Version 1.999.123:
+
+- NCD: performance improvements related to finding modules for statements
+
+- NCD: performance improvements related to resolving object names
+
+- NCD: performance improvements related to instantiating statement arguments
+
+- NCD: add value::replace_this() and value::replace_this_undo()
+
+- NCD: add value::reset()
+
+- NCD: add value::replace() and value::replace_undo()
+
+- Port to compile with MSVC for Windows.
+
+- NCD: add Foreach clause
+
+- NCD: implement _caller in spawn(), add spawn::join()
+
+- NCD: add explode()
+
+- NCD: add hard_reboot() and hard_poweroff()
+
+- NCD: add file_stat() and file_lstat()
+
+- NCD: fix regex_replace() semantics. It was very broken because it did a complete replacement pass for every regex on the list, so it would match parts that have already been replaced, producing unexpected results.
+
+- NCD: small performance improvement
+
+Version 1.999.121:
+
+- NCD: improve error handling semantics; see http://code.google.com/p/badvpn/source/detail?r=1376
+
+- NCD: fix assertion failure in sys.evdev() if a device error occurs (e.g. device unplugged) while an event is being processed. Similar fix in some other modules, but these may not be reproducable.
+
+- NCD: some more performance improvements
+
+- NCD: some performance improvements (~30% faster interpretation of cpu-bound code)
+
+- NCD: implemented If..elif..else clause.
+
+- NCD: net.backend.wpa_supplicant: fix to work with wpa_supplicant>=1.0
+
+Version 1.999.115:
+
+- NCD: Many improvements; new statements, including call(), alias(), foreach(), choose().
+
+Version 1.999.113:
+
+- NCD: when starting child processes, make sure that file descriptors for standard
+  streams are always open in the child, by opening /dev/null if they are not.
+
+- Improve build system to allow selective building of components.
+  By default, everything is built, unless -DBUILD_NOTHING_BY_DEFAULT=1 is given.
+  Individual components can then be enabled or disabled using -DBUILD_COMPONENT=1
+  and -DBUILD_COMPONENT=0.
+
+- When starting any BadVPN program, make sure that file descriptors for standard
+  streams are always open in the child, by opening /dev/null if they are not.
+
+- NCD: net.backend.wpa_supplicant(): add 'bssid' and 'ssid' variables to allow
+  determining what wireless network wpa_supplicant connected to.
+
+- NCD: net.backend.wpa_supplicant(): do not require the user to start wpa_supplicant via
+  stdbuf, but do it automatically.
+
+Version 1.999.111:
+
+- Improved protocol such that peers can use SSL when comminicating via the server. This
+  improves security, as compromising the server will not allow the attacker to see secret
+  data shared by peers (in particular, encryption keys and OTP seeds when in UDP mode).
+
+  Compatibility is preserved if an only if the following conditions are met:
+  - The server is using the latest version.
+  - If the network is using SSL, all clients using the new version are using the
+    "--allow-peer-talk-without-ssl" command line option.
+
+  Be aware, however, that using the "--allow-peer-talk-without-ssl" option negates the
+  security benefits of the new SSL support - not only between pairs of peers where one
+  peer is using the old version, but also between pairs where both peers are capable
+  of SSL. This is because the server can re-initialize the pair, telling them not to use
+  SSL.
+
+Version 1.999.107:
+
+- Added Windows IOCP support, removing the limitation on ~64 connections. This is important
+  for tun2socks, which may have to handle several hundred connections.
+
+Version 1.999.105.2:
+
+- Fixed an assertion failure in tun2socks related to sending data to SOCKS.
+
+Version 1.999.101.3:
+
+- Fixed UDP transport on Windows 7 which didn't work (was only tested on XP).
+
+Version 1.999.101:
+
+- Fixed a protocol issue present in versions <=1.999.100.3. Compatibility is preserved in
+  case of a new server and old clients, but it is not possible to connect to an old server
+  with a new client.
diff --git a/external/badvpn_dns/INSTALL b/external/badvpn_dns/INSTALL
new file mode 100644
index 0000000..3605f4e
--- /dev/null
+++ b/external/badvpn_dns/INSTALL
@@ -0,0 +1,76 @@
+1 Requirements
+
+1.1 Operating system
+
+Linux:
+- Linux kernel 2.6. Kernel 2.4 will work, but performance will suffer.
+- tested on x86, x86_64 and ARM architectures. Not tested on any big-endian architecture.
+
+Windows:
+- Windows XP or newer; tested on Windows XP and Windows 7
+
+FreeBSD:
+- Not regularly tested.
+
+Other systems are not supported.
+
+1.2 Compilers
+
+Linux:
+  - gcc
+  - clang, except >=3.0 (clang bug http://llvm.org/bugs/show_bug.cgi?id=11535)
+
+Windows:
+  - gcc from the mingw-w64 project for 32-bit targets
+
+C language features used:
+  - Standard (all part of C99):
+    - designated initializers
+    - stdint.h, inttypes.h, stddef.h
+    - intermingled declarations and code
+    - for loop initial declaration
+    - one-line "//" comments
+  - Extensions:
+    - packed structure attribute (to pack a structure and allow unaligned access)
+
+1.3 CMake
+
+The build system uses CMake.
+
+1.4 OpenSSL
+
+Libcrypto (part of OpenSSL) is used for block ciphers, hash functions and random data generation.
+
+1.5 Network Security Services (NSS)
+
+The NSS library from Mozilla is used for TLS support. NSS command-line tools are also needed
+for setting up certificates.
+
+1.6 TAP-Win32 (Windows only) (runtime only)
+
+The TAP-Win32 driver, part of OpenVPN.
+
+2 Compilation
+
+2.1 Compiling on Linux
+
+$ tar xf badvpn-<version>.tar.bz2
+$ mkdir build
+$ cd build
+$ cmake ../badvpn-<version> -DCMAKE_INSTALL_PREFIX=/usr/local
+$ make
+If you want to install it, run as root:
+# make install
+
+If you only want NCD or tun2socks and not the VPN system, you can avoid the NSS dependency by passing
+the following to the cmake command:
+-DBUILD_NCD=1 -DBUILD_TUN2SOCKS=1 -DBUILD_NOTHING_BY_DEFAULT=1
+
+2.2 Compiling for Windows
+
+See the file INSTALL-WINDOWS for detailed instructions.
+
+3 Usage
+
+The primary documentation is on the BadVPN homepage, http://code.google.com/p/badvpn/ .
+Additionally, some man pages are installed (badvpn(7), badvpn-server(8), badvpn-client(8)).
diff --git a/external/badvpn_dns/INSTALL-WINDOWS b/external/badvpn_dns/INSTALL-WINDOWS
new file mode 100644
index 0000000..9f0d5cf
--- /dev/null
+++ b/external/badvpn_dns/INSTALL-WINDOWS
@@ -0,0 +1,72 @@
+There are many ways to build BadVPN for Windows. It can be built with MSVC or GCC compilers,
+and it be built natively from Windows or cross-compiled from Linux. However, this document
+only describes building natively from Windows using MSVC.
+
+1. Get a MSVC compiler, e.g. from Visual Studio, Visual Studio Express or from the Windows SDK.
+
+2. Choose a directory where built stuff will be installed into; we call it <root>.
+
+3. Build the NSS library.
+   NOTE: you can also use the prebuilt version in the BadVPN windows download.
+
+    - Install MozillaBuild:
+        http://ftp.mozilla.org/pub/mozilla.org/mozilla/libraries/win32/MozillaBuildSetup-Latest.exe .
+
+    - Download the NSS source code that includes NSPR. As of the time of writing the latest version was 3.13.5:
+      https://ftp.mozilla.org/pub/mozilla.org/security/nss/releases/NSS_3_13_5_RTM/src/nss-3.13.5-with-nspr-4.9.1.tar.gz .
+
+      Extract it to c:\ so that you have C:\mozilla .
+
+    - Open a terminal with access to the Visual Studio compilers and other tools. E.g. if you use the Windows SDK,
+      activate the following start menu item: Programs -> Microsoft Windows SDK v7.1 -> Windows SDK 7.1 Command Prompt.
+
+    - In this terminal, run:
+
+      > c:\mozilla-build\start-l10n.bat
+
+    - Either a new terminal opens with a bash shell, or a bash shell starts in the existing terminal. Either way,
+      enter the following commands to finally build NSS: (here paths are written as /driveletter/...)
+
+      $ export OS_TARGET=WINNT
+      $ export BUILD_OPT=1
+      $ cd <nss_source_dir>/mozilla/security/nss
+      $ make nss_build_all
+
+      Now use a script shipped with the BadVPN source to copy the resulting files into appropriate directories within <root>:
+
+      $ <badvpn_source_dir>/scripts/copy_nss ../../dist <root>
+
+4. Build the OpenSSL library.
+   NOTE: you can also use the prebuilt version in the BadVPN windows download.
+
+    - Install ActivePerl.
+
+    - Download the OpenSSL source code and extract it.
+
+    - Open a compiler terminal, as was done when building NSS. Inside it, run:
+
+      > cd <openssl_source_dir>
+      > perl Configure VC-WIN32 --prefix=<root>
+      > ms\do_ms
+      > nmake -f ms\ntdll.mak
+
+      To copy the results into <root>:
+
+      > nmake -f ms\ntdll.mak install
+
+5. Build BadVPN.
+
+    - Install CMake. During installation, select the option to include cmake in PATH
+      to avoid having to type a long path into the terminal.
+
+    - Create an empty folder where BadVPN will be built; call it <build>.
+
+    - Open a compiler terminal. Inside it, run:
+
+      > cd <build>
+      > cmake <badvpn_source_dir> -G "NMake Makefiles" -DCMAKE_INSTALL_PREFIX=<root> -DCMAKE_BUILD_TYPE=Release
+      > nmake
+
+      To copy the results into <root>:
+
+      > nmake install
diff --git a/external/badvpn_dns/arpprobe/BArpProbe.c b/external/badvpn_dns/arpprobe/BArpProbe.c
new file mode 100644
index 0000000..2e6feb4
--- /dev/null
+++ b/external/badvpn_dns/arpprobe/BArpProbe.c
@@ -0,0 +1,359 @@
+/**
+ * @file BArpProbe.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <sys/ioctl.h>
+#include <linux/filter.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/ethernet_proto.h>
+#include <misc/ipv4_proto.h>
+#include <misc/udp_proto.h>
+#include <misc/get_iface_info.h>
+#include <base/BLog.h>
+
+#include "BArpProbe.h"
+
+#include <generated/blog_channel_BArpProbe.h>
+
+#define STATE_INITIAL 1
+#define STATE_NOEXIST 2
+#define STATE_EXIST 3
+#define STATE_EXIST_PANIC 4
+
+static void dgram_handler (BArpProbe *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    BLog(BLOG_ERROR, "packet socket error");
+    
+    // report error
+    DEBUGERROR(&o->d_err, o->handler(o->user, BARPPROBE_EVENT_ERROR));
+    return;
+}
+
+static void send_request (BArpProbe *o)
+{
+    if (o->send_sending) {
+        BLog(BLOG_ERROR, "cannot send packet while another packet is being sent!");
+        return;
+    }
+    
+    // build packet
+    struct arp_packet *arp = &o->send_packet;
+    arp->hardware_type = hton16(ARP_HARDWARE_TYPE_ETHERNET);
+    arp->protocol_type = hton16(ETHERTYPE_IPV4);
+    arp->hardware_size = hton8(6);
+    arp->protocol_size = hton8(4);
+    arp->opcode = hton16(ARP_OPCODE_REQUEST);
+    memcpy(arp->sender_mac, o->if_mac, 6);
+    arp->sender_ip = hton32(0);
+    memset(arp->target_mac, 0, sizeof(arp->target_mac));
+    arp->target_ip = o->addr;
+    
+    // send packet
+    PacketPassInterface_Sender_Send(o->send_if, (uint8_t *)&o->send_packet, sizeof(o->send_packet));
+    
+    // set sending
+    o->send_sending = 1;
+}
+
+static void send_if_handler_done (BArpProbe *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->send_sending)
+    
+    // set not sending
+    o->send_sending = 0;
+}
+
+static void recv_if_handler_done (BArpProbe *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= sizeof(struct arp_packet))
+    
+    // receive next packet
+    PacketRecvInterface_Receiver_Recv(o->recv_if, (uint8_t *)&o->recv_packet);
+    
+    if (data_len != sizeof(struct arp_packet)) {
+        BLog(BLOG_WARNING, "receive: wrong size");
+        return;
+    }
+    
+    struct arp_packet *arp = &o->recv_packet;
+    
+    if (ntoh16(arp->hardware_type) != ARP_HARDWARE_TYPE_ETHERNET) {
+        BLog(BLOG_WARNING, "receive: wrong hardware type");
+        return;
+    }
+    
+    if (ntoh16(arp->protocol_type) != ETHERTYPE_IPV4) {
+        BLog(BLOG_WARNING, "receive: wrong protocol type");
+        return;
+    }
+    
+    if (ntoh8(arp->hardware_size) != 6) {
+        BLog(BLOG_WARNING, "receive: wrong hardware size");
+        return;
+    }
+    
+    if (ntoh8(arp->protocol_size) != 4) {
+        BLog(BLOG_WARNING, "receive: wrong protocol size");
+        return;
+    }
+    
+    if (ntoh16(arp->opcode) != ARP_OPCODE_REPLY) {
+        return;
+    }
+    
+    if (arp->sender_ip != o->addr) {
+        return;
+    }
+    
+    int old_state = o->state;
+    
+    // set minus one missed
+    o->num_missed = -1;
+    
+    // set timer
+    BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_EXIST_WAITSEND);
+    
+    // set state exist
+    o->state = STATE_EXIST;
+    
+    // report exist if needed
+    if (old_state == STATE_INITIAL || old_state == STATE_NOEXIST) {
+        o->handler(o->user, BARPPROBE_EVENT_EXIST);
+        return;
+    }
+}
+
+static void timer_handler (BArpProbe *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // send request
+    send_request(o);
+    
+    switch (o->state) {
+        case STATE_INITIAL: {
+            ASSERT(o->num_missed >= 0)
+            ASSERT(o->num_missed < BARPPROBE_INITIAL_NUM_ATTEMPTS)
+            
+            // increment missed
+            o->num_missed++;
+            
+            // all attempts failed?
+            if (o->num_missed == BARPPROBE_INITIAL_NUM_ATTEMPTS) {
+                // set timer
+                BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_NOEXIST_WAITRECV);
+                
+                // set state noexist
+                o->state = STATE_NOEXIST;
+                
+                // report noexist
+                o->handler(o->user, BARPPROBE_EVENT_NOEXIST);
+                return;
+            }
+            
+            // set timer
+            BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_INITIAL_WAITRECV);
+        } break;
+        
+        case STATE_NOEXIST: {
+            // set timer
+            BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_NOEXIST_WAITRECV);
+        } break;
+        
+        case STATE_EXIST: {
+            ASSERT(o->num_missed >= -1)
+            ASSERT(o->num_missed < BARPPROBE_EXIST_NUM_NOREPLY)
+            
+            // increment missed
+            o->num_missed++;
+            
+            // all missed?
+            if (o->num_missed == BARPPROBE_EXIST_NUM_NOREPLY) {
+                // set timer
+                BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_EXIST_PANIC_WAITRECV);
+                
+                // set zero missed
+                o->num_missed = 0;
+                
+                // set state panic
+                o->state = STATE_EXIST_PANIC;
+                return;
+            }
+            
+            // set timer
+            BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_EXIST_WAITRECV);
+        } break;
+        
+        case STATE_EXIST_PANIC: {
+            ASSERT(o->num_missed >= 0)
+            ASSERT(o->num_missed < BARPPROBE_EXIST_PANIC_NUM_NOREPLY)
+            
+            // increment missed
+            o->num_missed++;
+            
+            // all missed?
+            if (o->num_missed == BARPPROBE_EXIST_PANIC_NUM_NOREPLY) {
+                // set timer
+                BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_NOEXIST_WAITRECV);
+                
+                // set state panic
+                o->state = STATE_NOEXIST;
+                
+                // report noexist
+                o->handler(o->user, BARPPROBE_EVENT_NOEXIST);
+                return;
+            }
+            
+            // set timer
+            BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_EXIST_PANIC_WAITRECV);
+        } break;
+    }
+}
+
+int BArpProbe_Init (BArpProbe *o, const char *ifname, uint32_t addr, BReactor *reactor, void *user, BArpProbe_handler handler)
+{
+    ASSERT(ifname)
+    ASSERT(handler)
+    
+    // init arguments
+    o->addr = addr;
+    o->reactor = reactor;
+    o->user = user;
+    o->handler = handler;
+    
+    // get interface information
+    int if_mtu;
+    int if_index;
+    if (!badvpn_get_iface_info(ifname, o->if_mac, &if_mtu, &if_index)) {
+        BLog(BLOG_ERROR, "failed to get interface information");
+        goto fail0;
+    }
+    
+    uint8_t *if_mac = o->if_mac;
+    BLog(BLOG_INFO, "if_mac=%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8" if_mtu=%d if_index=%d",
+         if_mac[0], if_mac[1], if_mac[2], if_mac[3], if_mac[4], if_mac[5], if_mtu, if_index);
+    
+    // check MTU
+    if (if_mtu < sizeof(struct arp_packet)) {
+        BLog(BLOG_ERROR, "MTU is too small for ARP !?!");
+        goto fail0;
+    }
+    
+    // init dgram
+    if (!BDatagram_Init(&o->dgram, BADDR_TYPE_PACKET, o->reactor, o, (BDatagram_handler)dgram_handler)) {
+        BLog(BLOG_ERROR, "BDatagram_Init failed");
+        goto fail0;
+    }
+    
+    // bind dgram
+    BAddr bind_addr;
+    BAddr_InitPacket(&bind_addr, hton16(ETHERTYPE_ARP), if_index, BADDR_PACKET_HEADER_TYPE_ETHERNET, BADDR_PACKET_PACKET_TYPE_HOST, if_mac);
+    if (!BDatagram_Bind(&o->dgram, bind_addr)) {
+        BLog(BLOG_ERROR, "BDatagram_Bind failed");
+        goto fail1;
+    }
+    
+    // set dgram send addresses
+    BAddr dest_addr;
+    uint8_t broadcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+    BAddr_InitPacket(&dest_addr, hton16(ETHERTYPE_ARP), if_index, BADDR_PACKET_HEADER_TYPE_ETHERNET, BADDR_PACKET_PACKET_TYPE_BROADCAST, broadcast_mac);
+    BIPAddr local_addr;
+    BIPAddr_InitInvalid(&local_addr);
+    BDatagram_SetSendAddrs(&o->dgram, dest_addr, local_addr);
+    
+    // init send interface
+    BDatagram_SendAsync_Init(&o->dgram, sizeof(struct arp_packet));
+    o->send_if = BDatagram_SendAsync_GetIf(&o->dgram);
+    PacketPassInterface_Sender_Init(o->send_if, (PacketPassInterface_handler_done)send_if_handler_done, o);
+    
+    // set not sending
+    o->send_sending = 0;
+    
+    // init recv interface
+    BDatagram_RecvAsync_Init(&o->dgram, sizeof(struct arp_packet));
+    o->recv_if = BDatagram_RecvAsync_GetIf(&o->dgram);
+    PacketRecvInterface_Receiver_Init(o->recv_if, (PacketRecvInterface_handler_done)recv_if_handler_done, o);
+    
+    // init timer
+    BTimer_Init(&o->timer, 0, (BTimer_handler)timer_handler, o);
+    
+    // receive first packet
+    PacketRecvInterface_Receiver_Recv(o->recv_if, (uint8_t *)&o->recv_packet);
+    
+    // send request
+    send_request(o);
+    
+    // set timer
+    BReactor_SetTimerAfter(o->reactor, &o->timer, BARPPROBE_INITIAL_WAITRECV);
+    
+    // set zero missed
+    o->num_missed = 0;
+    
+    // set state initial
+    o->state = STATE_INITIAL;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    BDatagram_Free(&o->dgram);
+fail0:
+    return 0;
+}
+
+void BArpProbe_Free (BArpProbe *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    // free timer
+    BReactor_RemoveTimer(o->reactor, &o->timer);
+    
+    // free recv interface
+    BDatagram_RecvAsync_Free(&o->dgram);
+    
+    // free send interface
+    BDatagram_SendAsync_Free(&o->dgram);
+    
+    // free dgram
+    BDatagram_Free(&o->dgram);
+}
diff --git a/external/badvpn_dns/arpprobe/BArpProbe.h b/external/badvpn_dns/arpprobe/BArpProbe.h
new file mode 100644
index 0000000..2ec3ffa
--- /dev/null
+++ b/external/badvpn_dns/arpprobe/BArpProbe.h
@@ -0,0 +1,80 @@
+/**
+ * @file BArpProbe.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_BARPPROBE_H
+#define BADVPN_BARPPROBE_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <misc/arp_proto.h>
+#include <misc/ethernet_proto.h>
+#include <base/DebugObject.h>
+#include <system/BDatagram.h>
+#include <system/BReactor.h>
+
+#define BARPPROBE_INITIAL_WAITRECV 1000
+#define BARPPROBE_INITIAL_NUM_ATTEMPTS 6
+#define BARPPROBE_NOEXIST_WAITRECV 15000
+#define BARPPROBE_EXIST_WAITSEND 15000
+#define BARPPROBE_EXIST_WAITRECV 10000
+#define BARPPROBE_EXIST_NUM_NOREPLY 2
+#define BARPPROBE_EXIST_PANIC_WAITRECV 1000
+#define BARPPROBE_EXIST_PANIC_NUM_NOREPLY 6
+
+#define BARPPROBE_EVENT_EXIST 1
+#define BARPPROBE_EVENT_NOEXIST 2
+#define BARPPROBE_EVENT_ERROR 3
+
+typedef void (*BArpProbe_handler) (void *user, int event);
+
+typedef struct {
+    uint32_t addr;
+    BReactor *reactor;
+    void *user;
+    BArpProbe_handler handler;
+    BDatagram dgram;
+    uint8_t if_mac[6];
+    PacketPassInterface *send_if;
+    int send_sending;
+    struct arp_packet send_packet;
+    PacketRecvInterface *recv_if;
+    struct arp_packet recv_packet;
+    BTimer timer;
+    int state;
+    int num_missed;
+    DebugError d_err;
+    DebugObject d_obj;
+} BArpProbe;
+
+int BArpProbe_Init (BArpProbe *o, const char *ifname, uint32_t addr, BReactor *reactor, void *user, BArpProbe_handler handler) WARN_UNUSED;
+void BArpProbe_Free (BArpProbe *o);
+
+#endif
diff --git a/external/badvpn_dns/arpprobe/CMakeLists.txt b/external/badvpn_dns/arpprobe/CMakeLists.txt
new file mode 100644
index 0000000..a090f10
--- /dev/null
+++ b/external/badvpn_dns/arpprobe/CMakeLists.txt
@@ -0,0 +1 @@
+badvpn_add_library(arpprobe "base;system;flow" "" BArpProbe.c)
diff --git a/external/badvpn_dns/badvpn.7 b/external/badvpn_dns/badvpn.7
new file mode 100644
index 0000000..c421a35
--- /dev/null
+++ b/external/badvpn_dns/badvpn.7
@@ -0,0 +1,324 @@
+.TH badvpn 7 "6 October 2010"
+.SH NAME
+BadVPN - peer-to-peer VPN system
+.SH DESCRIPTION
+.P
+BadVPN is a peer-to-peer VPN system. It provides a Layer 2 (Ethernet) network between
+the peers (VPN network nodes). The peers connect to a central server which acts as a chat
+server for them to establish direct connections between each other (data connections).
+These connections are used for transferring network data (Ethernet frames).
+.SS "Features"
+.P
+.B "Data connections"
+.P
+Peers can transfer network data either over UDP or TCP. For both there are ways of
+securing the data (see below).
+.P
+.B "IPv6 support"
+.P
+IPv6 can be used for both server connections and data connections, alongside with IPv4.
+Additionally, both can be combined to allow gradual migration to IPv6.
+.P
+.B "Address selection"
+.P
+Because NATs and firewalls are widespread, it is harder for peer-to-peer services to operate.
+In general, for two computers to be able to communicate, one computer must
+.I bind
+to one of its addresses, and the other computer must
+.I connect
+to the computer that binded (both for TCP and UDP). In a network with point-to-point
+connectivity, the connecting computer can connect to the same address as the binding computer
+bound to, so it is sufficient for the binding computer to send its address to the connecting
+computer. However, NATs and firewalls break point-to-point connectivity. When a network is
+behind a NAT, it is, by default, impossible for computers outside of that network to connect
+to computers inside the network. This is because computers inside the network have no externally
+visible IP address, and only communicate with the outside world through the external IP address
+of the NAT router. It is however possible to manually configure the NAT router to
+.I forward
+a specific port number on its external IP address to a specific computer inside the network.
+This makes it possible for a computer outside of the network to connect to a computer inside
+a network, however, it must connect to the external address of the NAT router (rather than
+the address the computer inside bound to, which is its internal address). So there needs
+to be some way for the connecting peer to know what address to connect to.
+.P
+BadVPN solves this problem with so-called
+.IR "address scopes" "."
+The peer that binds must have a list of external addresses for each address it can bind to,
+possibly ordered from best to worst. Each external address has its scope name. A scope name
+represents part of a network from which an external address can be reached. On the other hand,
+the peer that connects must have a list of scopes which it can reach. When a peer binds to an
+address, it sends the other peer a list of external addresses along with scope names. That peer
+than chooses the first external address whose scope it recognizes and attempts to connect to it
+(if there is one).
+.P
+BadVPN also allows a peer to have multiple addresses for binding to. It is possible to specify
+both an IPv4 and an IPv6 address to work in a multi-protocol environment.
+.P
+.B "Relaying"
+.P
+BadVPN can be configured to allow pairs of peers that cannot communicate directly (i.e. because of
+NATs or firewalls) to relay network data through a third peer. Relaying is only attempted if
+none of the two peers recognize any of the other peer's external addresses (or there are none).
+For relaying to work, for each of the two peers (P1, other one P2) there must be at least one
+third peer (R) that P1 it is allowed to relay through and can communicate directly with, and all
+such peers R must be able to communicate directly with P2.
+.P
+.B "IGMP snooping"
+.P
+BadVPN nodes perform IGMP snooping in order to efficiently deliver multicast frames. For example,
+this makes it possible to use BadVPN as a tunnel into an IPTV network of an Internet Service Provider
+for you to watch TV from wherever you want (given sufficient link quality).
+.P
+.B "Code quality"
+.P
+BadVPN has great focus on code quality and reliability. BadVPN is written in the C programming
+language. It is a single-threaded event-driven program. This allows for low resource usage and
+fast response times. Even though C is a relatively low-level language, the programs are made of
+small, highly cohesive and loosely coupled modules that are combined into a complete program on
+a high level. Modules are accesed and communicate through small, simple and to-the-point interfaces.
+It utilizes a flow-based design which greatly simplifies processing of data and input and output
+of the programs.
+.SS "Security features"
+.P
+BadVPN contains many security features, all of which are optional. The included security
+features are described here.
+.P
+.B TLS for client-server connections
+.P
+It is possible for the peers to communicate with the chat server securely with TLS. It is
+highly recommended that this feature is used if any security whatsoever is needed. Not
+using it renders all other security features useless, since clients exchange keys
+unencrypted via the server. When enabled, the chat server requires each client to identify
+itself with a certificate.
+.P
+BadVPN uses Mozilla's NSS library for TLS support. This means that the required certificates
+and keys must be available in a NSS database. The database and certificates can be
+generated with the
+.B certutil
+command. See the examples section on how to generate and distribute the certificates.
+.P
+.B TLS for peer messaging
+.P
+If TLS is being used for client-server connections, it will also be used between each pair of
+peers communicating via the server, on top of the TLS connections to the server. This secures
+the messages from the server itself. It is important because the messages may include
+encryption keys and other private data.
+.P
+.B TLS for TCP data connections
+.P
+If TCP is used for data connections between the peers, the data connections can be secured
+with TLS. This requires using TLS for client-server connections. The clients need to trust
+each others' certificates to be able to connect. Additionally, each client must identify to
+its peers with the same certificates it used for connecting to the server.
+.P
+.B Encryption for UDP data connections
+.P
+If UDP is used for data connections, it is possible for each pair of peers to encrypt their
+UDP packets with a symmetric block cipher. Note that the encryption keys are transmitted
+through the server unencrypted, so for this to be useful, server connections must be secured
+with TLS. The encryption aims to prevent third parties from seeing the real contents of
+the network data being transfered.
+.P
+.B Hashes for UDP data connections
+.P
+If UDP is used for data connections, it is possible to include hashes in packets. Note that
+hashes are only useful together with encryption. If enabled, the hash is calculated on the
+packet with the hash field zeroed and then written to the hash field. Hashes are calculated
+and included before encryption (if enabled). Combined with encryption, hashes aim to prevent
+third parties from tampering with the packets and injecting them into the network.
+.P
+.B One-time passwords for UDP data connections
+.P
+If UDP is used for data connections, it is possible to include one-time passwords in packets.
+Note that for this to be useful, server connections must be secured with TLS.
+One-time passwords are generated from a seed value by encrypting zero data with a block cipher.
+The seed contains the encryption key for the block cipher and the initialization vector.
+Only a fixed number of passwords are used from a single seed. The peers exchange seeds through
+the server. One-time passwords aim to prevent replay attacks.
+.P
+.B Control over peer communication
+.P
+It is possible to instruct the chat server to only allow certain peers to communicate. This
+will break end-to-end connectivity in the virtual network. It is useful in certain cases
+to improve security, for example when the VPN is used only to allow clients to securely connect
+to a central service.
+.SH "EXAMPLES"
+.SS "Setting up certificates"
+.P
+If you want to use TLS for server connections (recommended), the server and all the peers will
+need certificates. This section explains how to generate and distribute the certificates using
+NSS command line tools.
+.P
+.B Setting up the Certificate Authority (CA)
+.P
+On the system that will host the CA, create a NSS database for the CA and generate a CA certificate
+valid for 24 months:
+.P
+vpnca $ certutil -d sql:/home/vpnca/nssdb -N
+.br
+vpnca $ certutil -d sql:/home/vpnca/nssdb -S -n "vpnca" -s "CN=vpnca" -t "TC,," -x -2 -v 24
+.br
+> Is this a CA certificate [y/N]? y
+.br
+> Enter the path length constraint, enter to skip [<0 for unlimited path]: > -1
+.br
+> Is this a critical extension [y/N]? n
+.P
+Export the public CA certificate (this file is public):
+.P
+vpnca $ certutil -d sql:/home/vpnca/nssdb -L -n vpnca -a > ca.pem
+.P
+.B Setting up the server certificate
+.P
+On the CA system, generate a certificate for the server valid for 24 months, with TLS server usage context:
+.P
+vpnca $ certutil -d sql:/home/vpnca/nssdb -S -n "<insert_server_name>" -s "CN=<insert_server_name>" -c "vpnca" -t ",," -2 -6 -v 24
+.br
+> 0
+.br
+> -1
+.br
+> Is this a critical extension [y/N]? n
+.br
+> Is this a CA certificate [y/N]? n
+.br
+> Enter the path length constraint, enter to skip [<0 for unlimited path]: >
+.br
+> Is this a critical extension [y/N]? n
+.P
+Export the server certificate to a PKCS#12 file (this file must be kept secret):
+.P
+vpnca $ pk12util -d sql:/home/vpnca/nssdb -o server.p12 -n "<insert_server_name>"
+.P
+On the system that will run the server, create a NSS database and import the CA certificate
+and the server cerificate:
+.P
+vpnserver $ certutil -d sql:/home/vpnserver/nssdb -N
+.br
+vpnserver $ certutil -d sql:/home/vpnserver/nssdb -A -t "CT,," -n "vpnca" -i /path/to/ca.pem
+.br
+vpnserver $ pk12util -d sql:/home/vpnserver/nssdb -i /path/to/server.p12
+.P
+.B Setting up peer certificates
+.P
+On the CA system, generate a certificate for the peer valid for 24 months, with TLS client and
+TLS server usage contexts:
+.P
+vpnca $ certutil -d sql:/home/vpnca/nssdb -S -n "peer-<insert_name>" -s "CN=peer-<insert_name>" -c "vpnca" -t ",," -2 -6 -v 24
+.br
+> 0
+.br
+> 1
+.br
+> -1
+.br
+> Is this a critical extension [y/N]? n
+.br
+> Is this a CA certificate [y/N]? n
+.br
+> Enter the path length constraint, enter to skip [<0 for unlimited path]: >
+.br
+> Is this a critical extension [y/N]? n
+.P
+Export the peer certificate to a PKCS#12 file (this file must be kept secret):
+.P
+vpnca $ pk12util -d sql:/home/vpnca/nssdb -o peer-<insert_name>.p12 -n "peer-<insert_name>"
+.P
+On the system that will run the VPN client, create a NSS database and import the CA certificate
+and the peer cerificate:
+.P
+vpnclient $ certutil -d sql:/home/vpnclient/nssdb -N
+.br
+vpnclient $ certutil -d sql:/home/vpnclient/nssdb -A -t "CT,," -n "vpnca" -i /path/to/ca.pem
+.br
+vpnclient $ pk12util -d sql:/home/vpnclient/nssdb -i /path/to/peer-<insert_name>.p12
+.SS "Setting up TAP devices"
+.P
+You need to create and configure TAP devices on all computers that will participate in the virtual network
+(i.e. run the client program). See
+.BR badvpn-client (8),
+section `TAP DEVICE CONFIGURATION` for details.
+.SS "Example: Local IPv4 network, UDP transport, zero security"
+.P
+.B Starting the server:
+.P
+badvpn-server --listen-addr 0.0.0.0:7000
+.P
+.B Starting the peers:
+.P
+badvpn-client
+.RS
+--server-addr <insert_server_local_address>:7000
+.br
+--transport-mode udp --encryption-mode none --hash-mode none
+.br
+--scope local1
+.br
+--bind-addr 0.0.0.0:8000 --num-ports 30 --ext-addr {server_reported}:8000 local1
+.br
+--tapdev tap0
+.RE
+.SS "Example: Adding TLS and UDP security"
+.P
+.B Starting the server (other options as above):
+.P
+badvpn-server ...
+.RS
+--ssl --nssdb sql:/home/vpnserver/nssdb --server-cert-name "<insert_server_name>"
+.RE
+.P
+.B Starting the peers (other options as above):
+.P
+badvpn-client ...
+.RS
+--ssl --nssdb sql:/home/vpnclient/nssdb --client-cert-name "peer-<insert_name>"
+.br
+--encryption-mode blowfish --hash-mode md5 --otp blowfish 3000 2000
+.RE
+.SS "Example: Multiple local networks behind NATs, all connected to the Internet"
+.P
+For each peer in the existing local network, configure the NAT router to forward its
+range of ports to it (assuming their port ranges do not overlap). The clients also need
+to know the external IP address of the NAT router. If you don't have a static one,
+you'll need to discover it before starting the clients. Also forward the server port to
+the server.
+.P
+.B Starting the peers in the local network (other options as above):
+.P
+badvpn-client
+.RS
+.RB "..."
+.br
+--scope internet
+.br
+.RB "..."
+.br
+--ext-addr <insert_NAT_routers_external_IP>:<insert_start_of_forwarded_port_range> internet
+.br
+.RB "..."
+.RE
+.P
+The --ext-addr option applies to the previously specified --bind-addr option, and must come after
+the first --ext-addr option which specifies a local address.
+.P
+Now perform a similar setup in some other local network behind a NAT. However:
+.br
+- Don't set up a new server, instead make the peers connect to the existing server in the first
+local network.
+.br
+- You can't use {server_reported} for the local address --ext-addr options, because the server
+would report the NAT router's external address rather than the peer's internal address. Instead
+each peer has to know its internal IP address.
+.br
+- Use a different scope name for it, e.g. "local2" instead of "local1".
+.P
+If setup correctly, all peers will be able to communicate: those in the same local network will
+communicate directly through local addresses, and those in different local networks will
+communicate through the Internet.
+.SH "PROTOCOL"
+The protocols used in BadVPN are described in the source code in the protocol/ directory.
+.SH "SEE ALSO"
+.BR badvpn-server (8),
+.BR badvpn-client (8)
+.SH AUTHORS
+Ambroz Bizjak <ambrop7 at gmail.com>
diff --git a/external/badvpn_dns/base/BLog.c b/external/badvpn_dns/base/BLog.c
new file mode 100644
index 0000000..94242d5
--- /dev/null
+++ b/external/badvpn_dns/base/BLog.c
@@ -0,0 +1,96 @@
+/**
+ * @file BLog.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+
+#include "BLog.h"
+
+#ifndef BADVPN_PLUGIN
+
+struct _BLog_channel blog_channel_list[] = {
+#include <generated/blog_channels_list.h>
+};
+
+struct _BLog_global blog_global = {
+    #ifndef NDEBUG
+    0
+    #endif
+};
+
+#endif
+
+// keep in sync with level numbers in BLog.h!
+static char *level_names[] = { NULL, "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG" };
+
+static void stdout_log (int channel, int level, const char *msg)
+{
+    fprintf(stdout, "%s(%s): %s\n", level_names[level], blog_global.channels[channel].name, msg);
+}
+
+static void stderr_log (int channel, int level, const char *msg)
+{
+    fprintf(stderr, "%s(%s): %s\n", level_names[level], blog_global.channels[channel].name, msg);
+}
+
+static void stdout_stderr_free (void)
+{
+}
+
+void BLog_InitStdout (void)
+{
+    BLog_Init(stdout_log, stdout_stderr_free);
+}
+
+void BLog_InitStderr (void)
+{
+    BLog_Init(stderr_log, stdout_stderr_free);
+}
+
+// ==== PSIPHON ====
+#ifdef PSIPHON
+
+void PsiphonLog(const char *level, const char *channel, const char *msg);
+
+static void psiphon_log (int channel, int level, const char *msg)
+{
+    PsiphonLog(level_names[level], blog_global.channels[channel].name, msg);
+}
+
+static void psiphon_free (void)
+{
+}
+
+void BLog_InitPsiphon (void)
+{
+    BLog_Init(psiphon_log, psiphon_free);
+}
+
+#endif
+// ==== PSIPHON ====
diff --git a/external/badvpn_dns/base/BLog.h b/external/badvpn_dns/base/BLog.h
new file mode 100644
index 0000000..dd2e4d0
--- /dev/null
+++ b/external/badvpn_dns/base/BLog.h
@@ -0,0 +1,402 @@
+/**
+ * @file BLog.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * A global object for logging.
+ */
+
+#ifndef BADVPN_BLOG_H
+#define BADVPN_BLOG_H
+
+#include <stdarg.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <base/BMutex.h>
+
+// auto-generated channel numbers and number of channels
+#include <generated/blog_channels_defines.h>
+
+// keep in sync with level names in BLog.c!
+#define BLOG_ERROR 1
+#define BLOG_WARNING 2
+#define BLOG_NOTICE 3
+#define BLOG_INFO 4
+#define BLOG_DEBUG 5
+
+#define BLog(...) BLog_LogToChannel(BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define BContextLog(context, ...) BLog_ContextLog((context), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+#define BLOG_CCCC(context) BLog_MakeChannelContext((context), BLOG_CURRENT_CHANNEL)
+
+typedef void (*_BLog_log_func) (int channel, int level, const char *msg);
+typedef void (*_BLog_free_func) (void);
+
+struct _BLog_channel {
+    const char *name;
+    int loglevel;
+};
+
+struct _BLog_global {
+    #ifndef NDEBUG
+    int initialized; // initialized statically
+    #endif
+    struct _BLog_channel channels[BLOG_NUM_CHANNELS];
+    _BLog_log_func log_func;
+    _BLog_free_func free_func;
+    BMutex mutex;
+#ifndef NDEBUG
+    int logging;
+#endif
+    char logbuf[2048];
+    int logbuf_pos;
+};
+
+extern struct _BLog_channel blog_channel_list[];
+extern struct _BLog_global blog_global;
+
+typedef void (*BLog_logfunc) (void *);
+
+typedef struct {
+    BLog_logfunc logfunc;
+    void *logfunc_user;
+} BLogContext;
+
+typedef struct {
+    BLogContext context;
+    int channel;
+} BLogChannelContext;
+
+static int BLogGlobal_GetChannelByName (const char *channel_name);
+
+static void BLog_Init (_BLog_log_func log_func, _BLog_free_func free_func);
+static void BLog_Free (void);
+static void BLog_SetChannelLoglevel (int channel, int loglevel);
+static int BLog_WouldLog (int channel, int level);
+static void BLog_Begin (void);
+static void BLog_AppendVarArg (const char *fmt, va_list vl);
+static void BLog_Append (const char *fmt, ...);
+static void BLog_AppendBytes (const char *data, size_t len);
+static void BLog_Finish (int channel, int level);
+static void BLog_LogToChannelVarArg (int channel, int level, const char *fmt, va_list vl);
+static void BLog_LogToChannel (int channel, int level, const char *fmt, ...);
+static void BLog_LogViaFuncVarArg (BLog_logfunc func, void *arg, int channel, int level, const char *fmt, va_list vl);
+static void BLog_LogViaFunc (BLog_logfunc func, void *arg, int channel, int level, const char *fmt, ...);
+static BLogContext BLog_RootContext (void);
+static BLogContext BLog_MakeContext (BLog_logfunc logfunc, void *logfunc_user);
+static void BLog_ContextLogVarArg (BLogContext context, int channel, int level, const char *fmt, va_list vl);
+static void BLog_ContextLog (BLogContext context, int channel, int level, const char *fmt, ...);
+static BLogChannelContext BLog_MakeChannelContext (BLogContext context, int channel);
+static void BLog_ChannelContextLogVarArg (BLogChannelContext ccontext, int level, const char *fmt, va_list vl);
+static void BLog_ChannelContextLog (BLogChannelContext ccontext, int level, const char *fmt, ...);
+
+void BLog_InitStdout (void);
+void BLog_InitStderr (void);
+
+// PSIPHON
+void BLog_InitPsiphon (void);
+
+int BLogGlobal_GetChannelByName (const char *channel_name)
+{
+    int i;
+    for (i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        if (!strcmp(blog_channel_list[i].name, channel_name)) {
+            return i;
+        }
+    }
+    
+    return -1;
+}
+
+void BLog_Init (_BLog_log_func log_func, _BLog_free_func free_func)
+{
+    ASSERT(!blog_global.initialized)
+    
+    #ifndef NDEBUG
+    blog_global.initialized = 1;
+    #endif
+    
+    // initialize channels
+    memcpy(blog_global.channels, blog_channel_list, BLOG_NUM_CHANNELS * sizeof(struct _BLog_channel));
+    
+    blog_global.log_func = log_func;
+    blog_global.free_func = free_func;
+#ifndef NDEBUG
+    blog_global.logging = 0;
+#endif
+    blog_global.logbuf_pos = 0;
+    blog_global.logbuf[0] = '\0';
+    
+    ASSERT_FORCE(BMutex_Init(&blog_global.mutex))
+}
+
+void BLog_Free (void)
+{
+    ASSERT(blog_global.initialized)
+#ifndef NDEBUG
+    ASSERT(!blog_global.logging)
+#endif
+    
+    BMutex_Free(&blog_global.mutex);
+    
+    #ifndef NDEBUG
+    blog_global.initialized = 0;
+    #endif
+    
+    blog_global.free_func();
+}
+
+void BLog_SetChannelLoglevel (int channel, int loglevel)
+{
+    ASSERT(blog_global.initialized)
+    ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS)
+    ASSERT(loglevel >= 0 && loglevel <= BLOG_DEBUG)
+    
+    blog_global.channels[channel].loglevel = loglevel;
+}
+
+int BLog_WouldLog (int channel, int level)
+{
+    ASSERT(blog_global.initialized)
+    ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS)
+    ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG)
+    
+    return (level <= blog_global.channels[channel].loglevel);
+}
+
+void BLog_Begin (void)
+{
+    ASSERT(blog_global.initialized)
+    
+    BMutex_Lock(&blog_global.mutex);
+    
+#ifndef NDEBUG
+    ASSERT(!blog_global.logging)
+    blog_global.logging = 1;
+#endif
+}
+
+void BLog_AppendVarArg (const char *fmt, va_list vl)
+{
+    ASSERT(blog_global.initialized)
+#ifndef NDEBUG
+    ASSERT(blog_global.logging)
+#endif
+    ASSERT(blog_global.logbuf_pos >= 0)
+    ASSERT(blog_global.logbuf_pos < sizeof(blog_global.logbuf))
+    
+    int w = vsnprintf(blog_global.logbuf + blog_global.logbuf_pos, sizeof(blog_global.logbuf) - blog_global.logbuf_pos, fmt, vl);
+    
+    if (w >= sizeof(blog_global.logbuf) - blog_global.logbuf_pos) {
+        blog_global.logbuf_pos = sizeof(blog_global.logbuf) - 1;
+    } else {
+        blog_global.logbuf_pos += w;
+    }
+}
+
+void BLog_Append (const char *fmt, ...)
+{
+    ASSERT(blog_global.initialized)
+#ifndef NDEBUG
+    ASSERT(blog_global.logging)
+#endif
+    
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_AppendVarArg(fmt, vl);
+    va_end(vl);
+}
+
+void BLog_AppendBytes (const char *data, size_t len)
+{
+    ASSERT(blog_global.initialized)
+#ifndef NDEBUG
+    ASSERT(blog_global.logging)
+#endif
+    ASSERT(blog_global.logbuf_pos >= 0)
+    ASSERT(blog_global.logbuf_pos < sizeof(blog_global.logbuf))
+    
+    size_t avail = (sizeof(blog_global.logbuf) - 1) - blog_global.logbuf_pos;
+    len = (len > avail ? avail : len);
+    
+    memcpy(blog_global.logbuf + blog_global.logbuf_pos, data, len);
+    blog_global.logbuf_pos += len;
+    blog_global.logbuf[blog_global.logbuf_pos] = '\0';
+}
+
+void BLog_Finish (int channel, int level)
+{
+    ASSERT(blog_global.initialized)
+#ifndef NDEBUG
+    ASSERT(blog_global.logging)
+#endif
+    ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS)
+    ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG)
+    ASSERT(BLog_WouldLog(channel, level))
+    
+    ASSERT(blog_global.logbuf_pos >= 0)
+    ASSERT(blog_global.logbuf_pos < sizeof(blog_global.logbuf))
+    ASSERT(blog_global.logbuf[blog_global.logbuf_pos] == '\0')
+    
+    blog_global.log_func(channel, level, blog_global.logbuf);
+    
+#ifndef NDEBUG
+    blog_global.logging = 0;
+#endif
+    blog_global.logbuf_pos = 0;
+    blog_global.logbuf[0] = '\0';
+    
+    BMutex_Unlock(&blog_global.mutex);
+}
+
+void BLog_LogToChannelVarArg (int channel, int level, const char *fmt, va_list vl)
+{
+    ASSERT(blog_global.initialized)
+    ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS)
+    ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG)
+    
+    if (!BLog_WouldLog(channel, level)) {
+        return;
+    }
+    
+    BLog_Begin();
+    BLog_AppendVarArg(fmt, vl);
+    BLog_Finish(channel, level);
+}
+
+void BLog_LogToChannel (int channel, int level, const char *fmt, ...)
+{
+    ASSERT(blog_global.initialized)
+    ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS)
+    ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG)
+    
+    if (!BLog_WouldLog(channel, level)) {
+        return;
+    }
+    
+    va_list vl;
+    va_start(vl, fmt);
+    
+    BLog_Begin();
+    BLog_AppendVarArg(fmt, vl);
+    BLog_Finish(channel, level);
+    
+    va_end(vl);
+}
+
+void BLog_LogViaFuncVarArg (BLog_logfunc func, void *arg, int channel, int level, const char *fmt, va_list vl)
+{
+    ASSERT(blog_global.initialized)
+    ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS)
+    ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG)
+    
+    if (!BLog_WouldLog(channel, level)) {
+        return;
+    }
+    
+    BLog_Begin();
+    func(arg);
+    BLog_AppendVarArg(fmt, vl);
+    BLog_Finish(channel, level);
+}
+
+void BLog_LogViaFunc (BLog_logfunc func, void *arg, int channel, int level, const char *fmt, ...)
+{
+    ASSERT(blog_global.initialized)
+    ASSERT(channel >= 0 && channel < BLOG_NUM_CHANNELS)
+    ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG)
+    
+    if (!BLog_WouldLog(channel, level)) {
+        return;
+    }
+    
+    va_list vl;
+    va_start(vl, fmt);
+    
+    BLog_Begin();
+    func(arg);
+    BLog_AppendVarArg(fmt, vl);
+    BLog_Finish(channel, level);
+    
+    va_end(vl);
+}
+
+static void BLog__root_logfunc (void *unused)
+{
+}
+
+static BLogContext BLog_RootContext (void)
+{
+    return BLog_MakeContext(BLog__root_logfunc, NULL);
+}
+
+static BLogContext BLog_MakeContext (BLog_logfunc logfunc, void *logfunc_user)
+{
+    ASSERT(logfunc)
+    
+    BLogContext context;
+    context.logfunc = logfunc;
+    context.logfunc_user = logfunc_user;
+    return context;
+}
+
+static void BLog_ContextLogVarArg (BLogContext context, int channel, int level, const char *fmt, va_list vl)
+{
+    BLog_LogViaFuncVarArg(context.logfunc, context.logfunc_user, channel, level, fmt, vl);
+}
+
+static void BLog_ContextLog (BLogContext context, int channel, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_ContextLogVarArg(context, channel, level, fmt, vl);
+    va_end(vl);
+}
+
+static BLogChannelContext BLog_MakeChannelContext (BLogContext context, int channel)
+{
+    BLogChannelContext ccontext;
+    ccontext.context = context;
+    ccontext.channel = channel;
+    return ccontext;
+}
+
+static void BLog_ChannelContextLogVarArg (BLogChannelContext ccontext, int level, const char *fmt, va_list vl)
+{
+    BLog_ContextLogVarArg(ccontext.context, ccontext.channel, level, fmt, vl);
+}
+
+static void BLog_ChannelContextLog (BLogChannelContext ccontext, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_ChannelContextLogVarArg(ccontext, level, fmt, vl);
+    va_end(vl);
+}
+
+#endif
diff --git a/external/badvpn_dns/base/BLog_syslog.c b/external/badvpn_dns/base/BLog_syslog.c
new file mode 100644
index 0000000..d7a954b
--- /dev/null
+++ b/external/badvpn_dns/base/BLog_syslog.c
@@ -0,0 +1,150 @@
+/**
+ * @file BLog_syslog.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <syslog.h>
+
+#include <misc/debug.h>
+
+#include "BLog_syslog.h"
+
+static int resolve_facility (char *str, int *out)
+{
+    if (!strcmp(str, "authpriv")) {
+        *out = LOG_AUTHPRIV;
+    }
+    else if (!strcmp(str, "cron")) {
+        *out = LOG_CRON;
+    }
+    else if (!strcmp(str, "daemon")) {
+        *out = LOG_DAEMON;
+    }
+    else if (!strcmp(str, "ftp")) {
+        *out = LOG_FTP;
+    }
+    else if (!strcmp(str, "local0")) {
+        *out = LOG_LOCAL0;
+    }
+    else if (!strcmp(str, "local1")) {
+        *out = LOG_LOCAL1;
+    }
+    else if (!strcmp(str, "local2")) {
+        *out = LOG_LOCAL2;
+    }
+    else if (!strcmp(str, "local3")) {
+        *out = LOG_LOCAL3;
+    }
+    else if (!strcmp(str, "local4")) {
+        *out = LOG_LOCAL4;
+    }
+    else if (!strcmp(str, "local5")) {
+        *out = LOG_LOCAL5;
+    }
+    else if (!strcmp(str, "local6")) {
+        *out = LOG_LOCAL6;
+    }
+    else if (!strcmp(str, "local7")) {
+        *out = LOG_LOCAL7;
+    }
+    else if (!strcmp(str, "lpr")) {
+        *out = LOG_LPR;
+    }
+    else if (!strcmp(str, "mail")) {
+        *out = LOG_MAIL;
+    }
+    else if (!strcmp(str, "news")) {
+        *out = LOG_NEWS;
+    }
+    else if (!strcmp(str, "syslog")) {
+        *out = LOG_SYSLOG;
+    }
+    else if (!strcmp(str, "user")) {
+        *out = LOG_USER;
+    }
+    else if (!strcmp(str, "uucp")) {
+        *out = LOG_UUCP;
+    }
+    else {
+        return 0;
+    }
+    
+    return 1;
+}
+
+static int convert_level (int level)
+{
+    ASSERT(level >= BLOG_ERROR && level <= BLOG_DEBUG)
+    
+    switch (level) {
+        case BLOG_ERROR:
+            return LOG_ERR;
+        case BLOG_WARNING:
+            return LOG_WARNING;
+        case BLOG_NOTICE:
+            return LOG_NOTICE;
+        case BLOG_INFO:
+            return LOG_INFO;
+        case BLOG_DEBUG:
+            return LOG_DEBUG;
+        default:
+            ASSERT(0)
+            return 0;
+    }
+}
+
+static struct {
+    char ident[200];
+} syslog_global;
+
+static void syslog_log (int channel, int level, const char *msg)
+{
+    syslog(convert_level(level), "%s: %s", blog_global.channels[channel].name, msg);
+}
+
+static void syslog_free (void)
+{
+    closelog();
+}
+
+int BLog_InitSyslog (char *ident, char *facility_str)
+{
+    int facility;
+    if (!resolve_facility(facility_str, &facility)) {
+        return 0;
+    }
+    
+    snprintf(syslog_global.ident, sizeof(syslog_global.ident), "%s", ident);
+    
+    openlog(syslog_global.ident, 0, facility);
+    
+    BLog_Init(syslog_log, syslog_free);
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/base/BLog_syslog.h b/external/badvpn_dns/base/BLog_syslog.h
new file mode 100644
index 0000000..1adf04e
--- /dev/null
+++ b/external/badvpn_dns/base/BLog_syslog.h
@@ -0,0 +1,42 @@
+/**
+ * @file BLog_syslog.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * BLog syslog backend.
+ */
+
+#ifndef BADVPN_BLOG_SYSLOG_H
+#define BADVPN_BLOG_SYSLOG_H
+
+#include <misc/debug.h>
+#include <base/BLog.h>
+
+int BLog_InitSyslog (char *ident, char *facility) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/base/BMutex.h b/external/badvpn_dns/base/BMutex.h
new file mode 100644
index 0000000..fbcbd05
--- /dev/null
+++ b/external/badvpn_dns/base/BMutex.h
@@ -0,0 +1,101 @@
+/**
+ * @file BMutex.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_BMUTEX_H
+#define BADVPN_BMUTEX_H
+
+#if !defined(BADVPN_THREAD_SAFE) || (BADVPN_THREAD_SAFE != 0 && BADVPN_THREAD_SAFE != 1)
+#error BADVPN_THREAD_SAFE is not defined or incorrect
+#endif
+
+#if BADVPN_THREAD_SAFE
+#include <pthread.h>
+#endif
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+
+typedef struct {
+#if BADVPN_THREAD_SAFE
+    pthread_mutex_t pthread_mutex;
+#endif
+    DebugObject d_obj;
+} BMutex;
+
+static int BMutex_Init (BMutex *o) WARN_UNUSED;
+static void BMutex_Free (BMutex *o);
+static void BMutex_Lock (BMutex *o);
+static void BMutex_Unlock (BMutex *o);
+
+static int BMutex_Init (BMutex *o)
+{
+#if BADVPN_THREAD_SAFE
+    if (pthread_mutex_init(&o->pthread_mutex, NULL) != 0) {
+        return 0;
+    }
+#endif
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+}
+
+static void BMutex_Free (BMutex *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+#if BADVPN_THREAD_SAFE
+    int res = pthread_mutex_destroy(&o->pthread_mutex);
+    B_USE(res)
+    ASSERT(res == 0)
+#endif
+}
+
+static void BMutex_Lock (BMutex *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+#if BADVPN_THREAD_SAFE
+    int res = pthread_mutex_lock(&o->pthread_mutex);
+    B_USE(res)
+    ASSERT(res == 0)
+#endif
+}
+
+static void BMutex_Unlock (BMutex *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+#if BADVPN_THREAD_SAFE
+    int res = pthread_mutex_unlock(&o->pthread_mutex);
+    B_USE(res)
+    ASSERT(res == 0)
+#endif
+}
+
+#endif
diff --git a/external/badvpn_dns/base/BPending.c b/external/badvpn_dns/base/BPending.c
new file mode 100644
index 0000000..6711604
--- /dev/null
+++ b/external/badvpn_dns/base/BPending.c
@@ -0,0 +1,205 @@
+/**
+ * @file BPending.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+
+#include "BPending.h"
+
+#include "BPending_list.h"
+#include <structure/SLinkedList_impl.h>
+
+void BPendingGroup_Init (BPendingGroup *g)
+{
+    // init jobs list
+    BPending__List_Init(&g->jobs);
+    
+    // init pending counter
+    DebugCounter_Init(&g->pending_ctr);
+    
+    // init debug object
+    DebugObject_Init(&g->d_obj);
+}
+
+void BPendingGroup_Free (BPendingGroup *g)
+{
+    DebugCounter_Free(&g->pending_ctr);
+    ASSERT(BPending__List_IsEmpty(&g->jobs))
+    DebugObject_Free(&g->d_obj);
+}
+
+int BPendingGroup_HasJobs (BPendingGroup *g)
+{
+    DebugObject_Access(&g->d_obj);
+    
+    return !BPending__List_IsEmpty(&g->jobs);
+}
+
+void BPendingGroup_ExecuteJob (BPendingGroup *g)
+{
+    ASSERT(!BPending__List_IsEmpty(&g->jobs))
+    DebugObject_Access(&g->d_obj);
+    
+    // get a job
+    BSmallPending *p = BPending__List_First(&g->jobs);
+    ASSERT(!BPending__ListIsRemoved(p))
+    ASSERT(p->pending)
+    
+    // remove from jobs list
+    BPending__List_RemoveFirst(&g->jobs);
+    
+    // set not pending
+    BPending__ListMarkRemoved(p);
+#ifndef NDEBUG
+    p->pending = 0;
+#endif
+    
+    // execute job
+    p->handler(p->user);
+    return;
+}
+
+BSmallPending * BPendingGroup_PeekJob (BPendingGroup *g)
+{
+    DebugObject_Access(&g->d_obj);
+    
+    return BPending__List_First(&g->jobs);
+}
+
+void BSmallPending_Init (BSmallPending *o, BPendingGroup *g, BSmallPending_handler handler, void *user)
+{
+    // init arguments
+    o->handler = handler;
+    o->user = user;
+    
+    // set not pending
+    BPending__ListMarkRemoved(o);
+#ifndef NDEBUG
+    o->pending = 0;
+#endif
+    
+    // increment pending counter
+    DebugCounter_Increment(&g->pending_ctr);
+    
+    // init debug object
+    DebugObject_Init(&o->d_obj);
+}
+
+void BSmallPending_Free (BSmallPending *o, BPendingGroup *g)
+{
+    DebugCounter_Decrement(&g->pending_ctr);
+    DebugObject_Free(&o->d_obj);
+    ASSERT(o->pending == !BPending__ListIsRemoved(o))
+    
+    // remove from jobs list
+    if (!BPending__ListIsRemoved(o)) {
+        BPending__List_Remove(&g->jobs, o);
+    }
+}
+
+void BSmallPending_SetHandler (BSmallPending *o, BSmallPending_handler handler, void *user)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // set handler
+    o->handler = handler;
+    o->user = user;
+}
+
+void BSmallPending_Set (BSmallPending *o, BPendingGroup *g)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->pending == !BPending__ListIsRemoved(o))
+    
+    // remove from jobs list
+    if (!BPending__ListIsRemoved(o)) {
+        BPending__List_Remove(&g->jobs, o);
+    }
+    
+    // insert to jobs list
+    BPending__List_Prepend(&g->jobs, o);
+    
+    // set pending
+#ifndef NDEBUG
+    o->pending = 1;
+#endif
+}
+
+void BSmallPending_Unset (BSmallPending *o, BPendingGroup *g)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->pending == !BPending__ListIsRemoved(o))
+    
+    if (!BPending__ListIsRemoved(o)) {
+        // remove from jobs list
+        BPending__List_Remove(&g->jobs, o);
+        
+        // set not pending
+        BPending__ListMarkRemoved(o);
+#ifndef NDEBUG
+        o->pending = 0;
+#endif
+    }
+}
+
+int BSmallPending_IsSet (BSmallPending *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->pending == !BPending__ListIsRemoved(o))
+    
+    return !BPending__ListIsRemoved(o);
+}
+
+void BPending_Init (BPending *o, BPendingGroup *g, BPending_handler handler, void *user)
+{
+    BSmallPending_Init(&o->base, g, handler, user);
+    o->g = g;
+}
+
+void BPending_Free (BPending *o)
+{
+    BSmallPending_Free(&o->base, o->g);
+}
+
+void BPending_Set (BPending *o)
+{
+    BSmallPending_Set(&o->base, o->g);
+}
+
+void BPending_Unset (BPending *o)
+{
+    BSmallPending_Unset(&o->base, o->g);
+}
+
+int BPending_IsSet (BPending *o)
+{
+    return BSmallPending_IsSet(&o->base);
+}
diff --git a/external/badvpn_dns/base/BPending.h b/external/badvpn_dns/base/BPending.h
new file mode 100644
index 0000000..07644be
--- /dev/null
+++ b/external/badvpn_dns/base/BPending.h
@@ -0,0 +1,250 @@
+/**
+ * @file BPending.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Module for managing a queue of jobs pending execution.
+ */
+
+#ifndef BADVPN_BPENDING_H
+#define BADVPN_BPENDING_H
+
+#include <stdint.h>
+
+#include <misc/debugcounter.h>
+#include <structure/SLinkedList.h>
+#include <base/DebugObject.h>
+
+struct BSmallPending_s;
+
+#include "BPending_list.h"
+#include <structure/SLinkedList_decl.h>
+
+/**
+ * Job execution handler.
+ * It is guaranteed that the associated {@link BSmallPending} object was
+ * in set state.
+ * The {@link BSmallPending} object enters not set state before the handler
+ * is called.
+ * 
+ * @param user as in {@link BSmallPending_Init}
+ */
+typedef void (*BSmallPending_handler) (void *user);
+
+/**
+ * Job execution handler.
+ * It is guaranteed that the associated {@link BPending} object was
+ * in set state.
+ * The {@link BPending} object enters not set state before the handler
+ * is called.
+ * 
+ * @param user as in {@link BPending_Init}
+ */
+typedef void (*BPending_handler) (void *user);
+
+/**
+ * Object that contains a list of jobs pending execution.
+ */
+typedef struct {
+    BPending__List jobs;
+    DebugCounter pending_ctr;
+    DebugObject d_obj;
+} BPendingGroup;
+
+/**
+ * Object for queuing a job for execution.
+ */
+typedef struct BSmallPending_s {
+    BPending_handler handler;
+    void *user;
+    BPending__ListNode pending_node; // optimization: if not pending, .next is this
+#ifndef NDEBUG
+    uint8_t pending;
+#endif
+    DebugObject d_obj;
+} BSmallPending;
+
+/**
+ * Object for queuing a job for execution. This is a convenience wrapper
+ * around {@link BSmallPending} with an extra field to remember the
+ * {@link BPendingGroup} being used.
+ */
+typedef struct {
+    BSmallPending base;
+    BPendingGroup *g;
+} BPending;
+
+/**
+ * Initializes the object.
+ * 
+ * @param g the object
+ */
+void BPendingGroup_Init (BPendingGroup *g);
+
+/**
+ * Frees the object.
+ * There must be no {@link BPending} or {@link BSmallPending} objects using
+ * this group.
+ * 
+ * @param g the object
+ */
+void BPendingGroup_Free (BPendingGroup *g);
+
+/**
+ * Checks if there is at least one job in the queue.
+ * 
+ * @param g the object
+ * @return 1 if there is at least one job, 0 if not
+ */
+int BPendingGroup_HasJobs (BPendingGroup *g);
+
+/**
+ * Executes the top job on the job list.
+ * The job is removed from the list and enters
+ * not set state before being executed.
+ * There must be at least one job in job list.
+ * 
+ * @param g the object
+ */
+void BPendingGroup_ExecuteJob (BPendingGroup *g);
+
+/**
+ * Returns the top job on the job list, or NULL if there are none.
+ * 
+ * @param g the object
+ * @return the top job if there is at least one job, NULL if not
+ */
+BSmallPending * BPendingGroup_PeekJob (BPendingGroup *g);
+
+/**
+ * Initializes the object.
+ * The object is initialized in not set state.
+ * 
+ * @param o the object
+ * @param g pending group to use
+ * @param handler job execution handler
+ * @param user value to pass to handler
+ */
+void BSmallPending_Init (BSmallPending *o, BPendingGroup *g, BSmallPending_handler handler, void *user);
+
+/**
+ * Frees the object.
+ * The execution handler will not be called after the object
+ * is freed.
+ * 
+ * @param o the object
+ * @param g pending group. Must be the same as was used in {@link BSmallPending_Init}.
+ */
+void BSmallPending_Free (BSmallPending *o, BPendingGroup *g);
+
+/**
+ * Changes the job execution handler.
+ * 
+ * @param o the object
+ * @param handler job execution handler
+ * @param user value to pass to handler
+ */
+void BSmallPending_SetHandler (BSmallPending *o, BSmallPending_handler handler, void *user);
+
+/**
+ * Enables the job, pushing it to the top of the job list.
+ * If the object was already in set state, the job is removed from its
+ * current position in the list before being pushed.
+ * The object enters set state.
+ * 
+ * @param o the object
+ * @param g pending group. Must be the same as was used in {@link BSmallPending_Init}.
+ */
+void BSmallPending_Set (BSmallPending *o, BPendingGroup *g);
+
+/**
+ * Disables the job, removing it from the job list.
+ * If the object was not in set state, nothing is done.
+ * The object enters not set state.
+ * 
+ * @param o the object
+ * @param g pending group. Must be the same as was used in {@link BSmallPending_Init}.
+ */
+void BSmallPending_Unset (BSmallPending *o, BPendingGroup *g);
+
+/**
+ * Checks if the job is in set state.
+ * 
+ * @param o the object
+ * @return 1 if in set state, 0 if not
+ */
+int BSmallPending_IsSet (BSmallPending *o);
+
+/**
+ * Initializes the object.
+ * The object is initialized in not set state.
+ * 
+ * @param o the object
+ * @param g pending group to use
+ * @param handler job execution handler
+ * @param user value to pass to handler
+ */
+void BPending_Init (BPending *o, BPendingGroup *g, BPending_handler handler, void *user);
+
+/**
+ * Frees the object.
+ * The execution handler will not be called after the object
+ * is freed.
+ * 
+ * @param o the object
+ */
+void BPending_Free (BPending *o);
+
+/**
+ * Enables the job, pushing it to the top of the job list.
+ * If the object was already in set state, the job is removed from its
+ * current position in the list before being pushed.
+ * The object enters set state.
+ * 
+ * @param o the object
+ */
+void BPending_Set (BPending *o);
+
+/**
+ * Disables the job, removing it from the job list.
+ * If the object was not in set state, nothing is done.
+ * The object enters not set state.
+ * 
+ * @param o the object
+ */
+void BPending_Unset (BPending *o);
+
+/**
+ * Checks if the job is in set state.
+ * 
+ * @param o the object
+ * @return 1 if in set state, 0 if not
+ */
+int BPending_IsSet (BPending *o);
+
+#endif
diff --git a/external/badvpn_dns/base/BPending_list.h b/external/badvpn_dns/base/BPending_list.h
new file mode 100644
index 0000000..eadac61
--- /dev/null
+++ b/external/badvpn_dns/base/BPending_list.h
@@ -0,0 +1,4 @@
+#define SLINKEDLIST_PARAM_NAME BPending__List
+#define SLINKEDLIST_PARAM_FEATURE_LAST 0
+#define SLINKEDLIST_PARAM_TYPE_ENTRY struct BSmallPending_s
+#define SLINKEDLIST_PARAM_MEMBER_NODE pending_node
diff --git a/external/badvpn_dns/base/CMakeLists.txt b/external/badvpn_dns/base/CMakeLists.txt
new file mode 100644
index 0000000..cf1f0f0
--- /dev/null
+++ b/external/badvpn_dns/base/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(BASE_ADDITIONAL_SOURCES)
+
+if (HAVE_SYSLOG_H)
+    list(APPEND BASE_ADDITIONAL_SOURCES BLog_syslog.c)
+endif ()
+
+set(BASE_SOURCES
+    DebugObject.c
+    BLog.c
+    BPending.c
+    ${BASE_ADDITIONAL_SOURCES}
+)
+badvpn_add_library(base "" "" "${BASE_SOURCES}")
diff --git a/external/badvpn_dns/base/DebugObject.c b/external/badvpn_dns/base/DebugObject.c
new file mode 100644
index 0000000..e694617
--- /dev/null
+++ b/external/badvpn_dns/base/DebugObject.c
@@ -0,0 +1,39 @@
+/**
+ * @file DebugObject.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DebugObject.h"
+
+#ifndef BADVPN_PLUGIN
+#ifndef NDEBUG
+DebugCounter debugobject_counter = DEBUGCOUNTER_STATIC;
+#if BADVPN_THREAD_SAFE
+pthread_mutex_t debugobject_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif
+#endif
+#endif
diff --git a/external/badvpn_dns/base/DebugObject.h b/external/badvpn_dns/base/DebugObject.h
new file mode 100644
index 0000000..b8db287
--- /dev/null
+++ b/external/badvpn_dns/base/DebugObject.h
@@ -0,0 +1,147 @@
+/**
+ * @file DebugObject.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object used for detecting leaks.
+ */
+
+#ifndef BADVPN_DEBUGOBJECT_H
+#define BADVPN_DEBUGOBJECT_H
+
+#include <stdint.h>
+
+#if !defined(BADVPN_THREAD_SAFE) || (BADVPN_THREAD_SAFE != 0 && BADVPN_THREAD_SAFE != 1)
+#error BADVPN_THREAD_SAFE is not defined or incorrect
+#endif
+
+#if BADVPN_THREAD_SAFE
+#include <pthread.h>
+#endif
+
+#include <misc/debug.h>
+#include <misc/debugcounter.h>
+
+#define DEBUGOBJECT_VALID UINT32_C(0x31415926)
+
+/**
+ * Object used for detecting leaks.
+ */
+typedef struct {
+    #ifndef NDEBUG
+    uint32_t c;
+    #endif
+} DebugObject;
+
+/**
+ * Initializes the object.
+ * 
+ * @param obj the object
+ */
+static void DebugObject_Init (DebugObject *obj);
+
+/**
+ * Frees the object.
+ * 
+ * @param obj the object
+ */
+static void DebugObject_Free (DebugObject *obj);
+
+/**
+ * Does nothing.
+ * 
+ * @param obj the object
+ */
+static void DebugObject_Access (const DebugObject *obj);
+
+/**
+ * Does nothing.
+ * There must be no {@link DebugObject}'s initialized.
+ */
+static void DebugObjectGlobal_Finish (void);
+
+#ifndef NDEBUG
+extern DebugCounter debugobject_counter;
+#if BADVPN_THREAD_SAFE
+extern pthread_mutex_t debugobject_mutex;
+#endif
+#endif
+
+void DebugObject_Init (DebugObject *obj)
+{
+    #ifndef NDEBUG
+    
+    obj->c = DEBUGOBJECT_VALID;
+    
+    #if BADVPN_THREAD_SAFE
+    ASSERT_FORCE(pthread_mutex_lock(&debugobject_mutex) == 0)
+    #endif
+    
+    DebugCounter_Increment(&debugobject_counter);
+    
+    #if BADVPN_THREAD_SAFE
+    ASSERT_FORCE(pthread_mutex_unlock(&debugobject_mutex) == 0)
+    #endif
+    
+    #endif
+}
+
+void DebugObject_Free (DebugObject *obj)
+{
+    ASSERT(obj->c == DEBUGOBJECT_VALID)
+    
+    #ifndef NDEBUG
+    
+    obj->c = 0;
+    
+    #if BADVPN_THREAD_SAFE
+    ASSERT_FORCE(pthread_mutex_lock(&debugobject_mutex) == 0)
+    #endif
+    
+    DebugCounter_Decrement(&debugobject_counter);
+    
+    #if BADVPN_THREAD_SAFE
+    ASSERT_FORCE(pthread_mutex_unlock(&debugobject_mutex) == 0)
+    #endif
+    
+    #endif
+}
+
+void DebugObject_Access (const DebugObject *obj)
+{
+    ASSERT(obj->c == DEBUGOBJECT_VALID)
+}
+
+void DebugObjectGlobal_Finish (void)
+{
+    #ifndef NDEBUG
+    DebugCounter_Free(&debugobject_counter);
+    #endif
+}
+
+#endif
diff --git a/external/badvpn_dns/blog_channels.txt b/external/badvpn_dns/blog_channels.txt
new file mode 100644
index 0000000..96313b5
--- /dev/null
+++ b/external/badvpn_dns/blog_channels.txt
@@ -0,0 +1,145 @@
+server 4
+client 4
+flooder 4
+tun2socks 4
+ncd 4
+ncd_var 4
+ncd_list 4
+ncd_depend 4
+ncd_multidepend 4
+ncd_dynamic_depend 4
+ncd_concat 4
+ncd_if 4
+ncd_strcmp 4
+ncd_regex_match 4
+ncd_logical 4
+ncd_sleep 4
+ncd_print 4
+ncd_blocker 4
+ncd_run 4
+ncd_runonce 4
+ncd_daemon 4
+ncd_spawn 4
+ncd_imperative 4
+ncd_ref 4
+ncd_index 4
+ncd_alias 4
+ncd_process_manager 4
+ncd_ondemand 4
+ncd_foreach 4
+ncd_choose 4
+ncd_net_backend_waitdevice 4
+ncd_net_backend_waitlink 4
+ncd_net_backend_badvpn 4
+ncd_net_backend_wpa_supplicant 4
+ncd_net_backend_rfkill 4
+ncd_net_up 4
+ncd_net_dns 4
+ncd_net_iptables 4
+ncd_net_ipv4_addr 4
+ncd_net_ipv4_route 4
+ncd_net_ipv4_dhcp 4
+ncd_net_ipv4_arp_probe 4
+ncd_net_watch_interfaces 4
+ncd_sys_watch_input 4
+ncd_sys_watch_usb 4
+ncd_sys_evdev 4
+ncd_sys_watch_directory 4
+StreamPeerIO 4
+DatagramPeerIO 4
+BReactor 3
+BSignal 3
+FragmentProtoAssembler 4
+BPredicate 3
+ServerConnection 4
+Listener 4
+DataProto 4
+FrameDecider 4
+BSocksClient 4
+BDHCPClientCore 4
+BDHCPClient 4
+NCDIfConfig 4
+BUnixSignal 4
+BProcess 4
+PRStreamSink 4
+PRStreamSource 4
+PacketProtoDecoder 4
+DPRelay 4
+BThreadWork 4
+DPReceive 4
+BInputProcess 4
+NCDUdevMonitorParser 4
+NCDUdevMonitor 4
+NCDUdevCache 4
+NCDUdevManager 4
+BTime 4
+BEncryption 4
+SPProtoDecoder 4
+LineBuffer 4
+BTap 4
+lwip 4
+NCDConfigTokenizer 4
+NCDConfigParser 4
+NCDValParser 4
+nsskey 4
+addr 4
+PasswordListener 4
+NCDInterfaceMonitor 4
+NCDRfkillMonitor 4
+udpgw 4
+UdpGwClient 4
+SocksUdpGwClient 4
+BNetwork 4
+BConnection 4
+BSSLConnection 4
+BDatagram 4
+PeerChat 4
+BArpProbe 4
+NCDModuleIndex 4
+NCDModuleProcess 4
+NCDValGenerator 4
+ncd_from_string 4
+ncd_to_string 4
+ncd_value 4
+ncd_try 4
+ncd_sys_request_server 4
+NCDRequest 4
+ncd_net_ipv6_wait_dynamic_addr 4
+NCDRequestClient 4
+ncd_request 4
+ncd_sys_request_client 4
+ncd_exit 4
+ncd_getargs 4
+ncd_arithmetic 4
+ncd_parse 4
+ncd_valuemetic 4
+ncd_file 4
+ncd_netmask 4
+ncd_implode 4
+ncd_call2 4
+ncd_assert 4
+ncd_reboot 4
+ncd_explode 4
+NCDPlaceholderDb 4
+NCDVal 4
+ncd_net_ipv6_addr 4
+ncd_net_ipv6_route 4
+ncd_net_ipv4_addr_in_network 4
+ncd_net_ipv6_addr_in_network 4
+dostest_server 4
+dostest_attacker 4
+ncd_timer 4
+ncd_file_open 4
+ncd_backtrack 4
+ncd_socket 4
+ncd_depend_scope 4
+ncd_substr 4
+ncd_sys_start_process 4
+NCDBuildProgram 4
+ncd_log 4
+ncd_log_msg 4
+ncd_buffer 4
+ncd_getenv 4
+BThreadSignal 4
+BLockReactor 4
+ncd_load_module 4
diff --git a/external/badvpn_dns/blog_generator/blog.php b/external/badvpn_dns/blog_generator/blog.php
new file mode 100644
index 0000000..fd436bc
--- /dev/null
+++ b/external/badvpn_dns/blog_generator/blog.php
@@ -0,0 +1,121 @@
+<?php
+
+require_once "blog_functions.php";
+
+function assert_failure ($script, $line, $message)
+{
+    if ($message == "") {
+        fatal_error("Assertion failure at {$script}:{$line}");
+    } else {
+        fatal_error("Assertion failure at {$script}:{$line}: {$message}");
+    }
+}
+
+assert_options(ASSERT_CALLBACK, "assert_failure");
+
+function print_help ($name)
+{
+    echo <<<EOD
+Usage: {$name}
+    --input-file <file>         Input channels file.
+    --output-dir <dir>          Destination directory for generated files.
+
+EOD;
+}
+
+$input_file = "";
+$output_dir = "";
+
+for ($i = 1; $i < $argc;) {
+    $arg = $argv[$i++];
+    switch ($arg) {
+        case "--input-file":
+            $input_file = $argv[$i++];
+            break;
+        case "--output-dir":
+            $output_dir = $argv[$i++];
+            break;
+        case "--help":
+            print_help($argv[0]);
+            exit(0);
+        default:
+            fatal_error("Unknown option: {$arg}");
+    }
+}
+
+if ($input_file == "") {
+    fatal_error("--input-file missing");
+}
+
+if ($output_dir == "") {
+    fatal_error("--output-dir missing");
+}
+
+if (($data = file_get_contents($input_file)) === FALSE) {
+    fatal_error("Failed to read input file");
+}
+
+if (!tokenize($data, $tokens)) {
+    fatal_error("Failed to tokenize");
+}
+
+$i = 0;
+$channels_defines = "";
+$channels_list = "";
+
+reset($tokens);
+
+while (1) {
+    if (($ch_name = current($tokens)) === FALSE) {
+        break;
+    }
+    next($tokens);
+    if (($ch_priority = current($tokens)) === FALSE) {
+        fatal_error("missing priority");
+    }
+    next($tokens);
+    if ($ch_name[0] != "name") {
+        fatal_error("name is not a name");
+    }
+    if ($ch_priority[0] != "number") {
+        fatal_error("priority is not a number");
+    }
+
+    $channel_file = <<<EOD
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_{$ch_name[1]}
+
+EOD;
+
+    $channels_defines .= <<<EOD
+#define BLOG_CHANNEL_{$ch_name[1]} {$i}
+
+EOD;
+
+    $channels_list .= <<<EOD
+{"{$ch_name[1]}", {$ch_priority[1]}},
+
+EOD;
+
+    if (file_put_contents("{$output_dir}/blog_channel_{$ch_name[1]}.h", $channel_file) === NULL) {
+        fatal_error("{$input_file}: Failed to write channel file");
+    }
+
+    $i++;
+}
+
+$channels_defines .= <<<EOD
+#define BLOG_NUM_CHANNELS {$i}
+
+EOD;
+
+if (file_put_contents("{$output_dir}/blog_channels_defines.h", $channels_defines) === NULL) {
+    fatal_error("{$input_file}: Failed to write channels defines file");
+}
+
+if (file_put_contents("{$output_dir}/blog_channels_list.h", $channels_list) === NULL) {
+    fatal_error("{$input_file}: Failed to write channels list file");
+}
+
diff --git a/external/badvpn_dns/blog_generator/blog_functions.php b/external/badvpn_dns/blog_generator/blog_functions.php
new file mode 100644
index 0000000..ba4be89
--- /dev/null
+++ b/external/badvpn_dns/blog_generator/blog_functions.php
@@ -0,0 +1,35 @@
+<?php
+
+function tokenize ($str, &$out) {
+    $out = array();
+
+    while (strlen($str) > 0) {
+        if (preg_match('/^\\/\\/.*/', $str, $matches)) {
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^\\s+/', $str, $matches)) {
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^[0-9]+/', $str, $matches)) {
+            $out[] = array('number', $matches[0]);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*/', $str, $matches)) {
+            $out[] = array('name', $matches[0]);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else {
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+function fatal_error ($message)
+{
+    fwrite(STDERR, "Fatal error: $message\n");
+
+    ob_get_clean();
+    exit(1);
+}
diff --git a/external/badvpn_dns/bproto/BProto.h b/external/badvpn_dns/bproto/BProto.h
new file mode 100644
index 0000000..5f2a696
--- /dev/null
+++ b/external/badvpn_dns/bproto/BProto.h
@@ -0,0 +1,85 @@
+/**
+ * @file BProto.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Definitions for BProto serialization.
+ */
+
+#ifndef BADVPN_BPROTO_BPROTO_H
+#define BADVPN_BPROTO_BPROTO_H
+
+#include <stdint.h>
+
+#include <misc/packed.h>
+
+#define BPROTO_TYPE_UINT8 1
+#define BPROTO_TYPE_UINT16 2
+#define BPROTO_TYPE_UINT32 3
+#define BPROTO_TYPE_UINT64 4
+#define BPROTO_TYPE_DATA 5
+#define BPROTO_TYPE_CONSTDATA 6
+
+B_START_PACKED
+struct BProto_header_s {
+    uint16_t id;
+    uint16_t type;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct BProto_uint8_s {
+    uint8_t v;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct BProto_uint16_s {
+    uint16_t v;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct BProto_uint32_s {
+    uint32_t v;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct BProto_uint64_s {
+    uint64_t v;
+} B_PACKED;
+B_END_PACKED
+
+B_START_PACKED
+struct BProto_data_header_s {
+    uint32_t len;
+} B_PACKED;
+B_END_PACKED
+
+#endif
diff --git a/external/badvpn_dns/bproto_generator/ProtoParser.lime b/external/badvpn_dns/bproto_generator/ProtoParser.lime
new file mode 100644
index 0000000..f039e1d
--- /dev/null
+++ b/external/badvpn_dns/bproto_generator/ProtoParser.lime
@@ -0,0 +1,99 @@
+%class ProtoParser
+%start file
+
+file =
+    directives messages {
+        $$ = array(
+            "directives" => $1,
+            "messages" => $2
+        );
+    }.
+
+directives =
+    {
+        $$ = array();
+    } |
+    directive semicolon directives {
+        $$ = array_merge(array($1), $3);
+    }.
+
+directive =
+    include string {
+        $$ = array(
+            "type" => "include",
+            "file" => $2
+        );
+    }.
+
+messages =
+    msgspec {
+        $$ = array($1);
+    } |
+    msgspec messages {
+        $$ = array_merge(array($1), $2);
+    }.
+
+msgspec =
+    message name spar entries epar semicolon {
+    $$ = array(
+        "name" => $2,
+        "entries" => $4
+    );
+}.
+
+entries =
+    entry {
+        $$ = array($1);
+    } |
+    entry entries {
+        $$ = array_merge(array($1), $2);
+    }.
+
+entry =
+    cardinality type name equals number semicolon {
+        $$ = array(
+            "cardinality" => $1,
+            "type" => $2,
+            "name" => $3,
+            "id" => $5
+        );
+    }.
+
+cardinality =
+    repeated {
+        $$ = "repeated";
+    } |
+    optional {
+        $$ = "optional";
+    } |
+    required {
+        $$ = "required";
+    } |
+    required repeated {
+        $$ = "required repeated";
+    }.
+
+type =
+    uint {
+        $$ = array(
+            "type" => "uint",
+            "size" => $1
+        );
+    } |
+    data {
+        $$ = array(
+            "type" => "data"
+        );
+    } |
+    data srpar string erpar {
+        $$ = array(
+            "type" => "constdata",
+            "size" => $3
+        );
+    } |
+    message name {
+        $$ = array(
+            "type" => "message",
+            "message" => $2
+        );
+    }.
diff --git a/external/badvpn_dns/bproto_generator/ProtoParser.php b/external/badvpn_dns/bproto_generator/ProtoParser.php
new file mode 100644
index 0000000..0477dba
--- /dev/null
+++ b/external/badvpn_dns/bproto_generator/ProtoParser.php
@@ -0,0 +1,560 @@
+<?php
+
+
+/*
+
+DON'T EDIT THIS FILE!
+
+This file was automatically generated by the Lime parser generator.
+The real source code you should be looking at is in one or more
+grammar files in the Lime format.
+
+THE ONLY REASON TO LOOK AT THIS FILE is to see where in the grammar
+file that your error happened, because there are enough comments to
+help you debug your grammar.
+
+If you ignore this warning, you're shooting yourself in the brain,
+not the foot.
+
+*/
+
+class ProtoParser extends lime_parser {
+var $qi = 0;
+var $i = array (
+  0 => 
+  array (
+    'directives' => 's 1',
+    'directive' => 's 30',
+    'include' => 's 33',
+    'file' => 's 35',
+    '\'start\'' => 'a \'start\'',
+    'message' => 'r 1',
+  ),
+  1 => 
+  array (
+    'messages' => 's 2',
+    'msgspec' => 's 3',
+    'message' => 's 5',
+  ),
+  2 => 
+  array (
+    '#' => 'r 0',
+  ),
+  3 => 
+  array (
+    'msgspec' => 's 3',
+    'messages' => 's 4',
+    'message' => 's 5',
+    '#' => 'r 4',
+  ),
+  4 => 
+  array (
+    '#' => 'r 5',
+  ),
+  5 => 
+  array (
+    'name' => 's 6',
+  ),
+  6 => 
+  array (
+    'spar' => 's 7',
+  ),
+  7 => 
+  array (
+    'entries' => 's 8',
+    'entry' => 's 11',
+    'cardinality' => 's 13',
+    'repeated' => 's 26',
+    'optional' => 's 27',
+    'required' => 's 28',
+  ),
+  8 => 
+  array (
+    'epar' => 's 9',
+  ),
+  9 => 
+  array (
+    'semicolon' => 's 10',
+  ),
+  10 => 
+  array (
+    'message' => 'r 6',
+    '#' => 'r 6',
+  ),
+  11 => 
+  array (
+    'entry' => 's 11',
+    'entries' => 's 12',
+    'cardinality' => 's 13',
+    'repeated' => 's 26',
+    'optional' => 's 27',
+    'required' => 's 28',
+    'epar' => 'r 7',
+  ),
+  12 => 
+  array (
+    'epar' => 'r 8',
+  ),
+  13 => 
+  array (
+    'type' => 's 14',
+    'uint' => 's 19',
+    'data' => 's 20',
+    'message' => 's 24',
+  ),
+  14 => 
+  array (
+    'name' => 's 15',
+  ),
+  15 => 
+  array (
+    'equals' => 's 16',
+  ),
+  16 => 
+  array (
+    'number' => 's 17',
+  ),
+  17 => 
+  array (
+    'semicolon' => 's 18',
+  ),
+  18 => 
+  array (
+    'repeated' => 'r 9',
+    'optional' => 'r 9',
+    'required' => 'r 9',
+    'epar' => 'r 9',
+  ),
+  19 => 
+  array (
+    'name' => 'r 14',
+  ),
+  20 => 
+  array (
+    'srpar' => 's 21',
+    'name' => 'r 15',
+  ),
+  21 => 
+  array (
+    'string' => 's 22',
+  ),
+  22 => 
+  array (
+    'erpar' => 's 23',
+  ),
+  23 => 
+  array (
+    'name' => 'r 16',
+  ),
+  24 => 
+  array (
+    'name' => 's 25',
+  ),
+  25 => 
+  array (
+    'name' => 'r 17',
+  ),
+  26 => 
+  array (
+    'uint' => 'r 10',
+    'data' => 'r 10',
+    'message' => 'r 10',
+  ),
+  27 => 
+  array (
+    'uint' => 'r 11',
+    'data' => 'r 11',
+    'message' => 'r 11',
+  ),
+  28 => 
+  array (
+    'repeated' => 's 29',
+    'uint' => 'r 12',
+    'data' => 'r 12',
+    'message' => 'r 12',
+  ),
+  29 => 
+  array (
+    'uint' => 'r 13',
+    'data' => 'r 13',
+    'message' => 'r 13',
+  ),
+  30 => 
+  array (
+    'semicolon' => 's 31',
+  ),
+  31 => 
+  array (
+    'directive' => 's 30',
+    'directives' => 's 32',
+    'include' => 's 33',
+    'message' => 'r 1',
+  ),
+  32 => 
+  array (
+    'message' => 'r 2',
+  ),
+  33 => 
+  array (
+    'string' => 's 34',
+  ),
+  34 => 
+  array (
+    'semicolon' => 'r 3',
+  ),
+  35 => 
+  array (
+    '#' => 'r 18',
+  ),
+);
+function reduce_0_file_1($tokens, &$result) {
+#
+# (0) file :=  directives  messages
+#
+$result = reset($tokens);
+
+    $result = array(
+        "directives" => $tokens[0],
+        "messages" => $tokens[1]
+    );
+
+}
+
+function reduce_1_directives_1($tokens, &$result) {
+#
+# (1) directives :=
+#
+$result = reset($tokens);
+
+        $result = array();
+    
+}
+
+function reduce_2_directives_2($tokens, &$result) {
+#
+# (2) directives :=  directive  semicolon  directives
+#
+$result = reset($tokens);
+
+        $result = array_merge(array($tokens[0]), $tokens[2]);
+    
+}
+
+function reduce_3_directive_1($tokens, &$result) {
+#
+# (3) directive :=  include  string
+#
+$result = reset($tokens);
+
+        $result = array(
+            "type" => "include",
+            "file" => $tokens[1]
+        );
+    
+}
+
+function reduce_4_messages_1($tokens, &$result) {
+#
+# (4) messages :=  msgspec
+#
+$result = reset($tokens);
+
+        $result = array($tokens[0]);
+    
+}
+
+function reduce_5_messages_2($tokens, &$result) {
+#
+# (5) messages :=  msgspec  messages
+#
+$result = reset($tokens);
+
+        $result = array_merge(array($tokens[0]), $tokens[1]);
+    
+}
+
+function reduce_6_msgspec_1($tokens, &$result) {
+#
+# (6) msgspec :=  message  name  spar  entries  epar  semicolon
+#
+$result = reset($tokens);
+
+    $result = array(
+        "name" => $tokens[1],
+        "entries" => $tokens[3]
+    );
+
+}
+
+function reduce_7_entries_1($tokens, &$result) {
+#
+# (7) entries :=  entry
+#
+$result = reset($tokens);
+
+        $result = array($tokens[0]);
+    
+}
+
+function reduce_8_entries_2($tokens, &$result) {
+#
+# (8) entries :=  entry  entries
+#
+$result = reset($tokens);
+
+        $result = array_merge(array($tokens[0]), $tokens[1]);
+    
+}
+
+function reduce_9_entry_1($tokens, &$result) {
+#
+# (9) entry :=  cardinality  type  name  equals  number  semicolon
+#
+$result = reset($tokens);
+
+        $result = array(
+            "cardinality" => $tokens[0],
+            "type" => $tokens[1],
+            "name" => $tokens[2],
+            "id" => $tokens[4]
+        );
+    
+}
+
+function reduce_10_cardinality_1($tokens, &$result) {
+#
+# (10) cardinality :=  repeated
+#
+$result = reset($tokens);
+
+        $result = "repeated";
+    
+}
+
+function reduce_11_cardinality_2($tokens, &$result) {
+#
+# (11) cardinality :=  optional
+#
+$result = reset($tokens);
+
+        $result = "optional";
+    
+}
+
+function reduce_12_cardinality_3($tokens, &$result) {
+#
+# (12) cardinality :=  required
+#
+$result = reset($tokens);
+
+        $result = "required";
+    
+}
+
+function reduce_13_cardinality_4($tokens, &$result) {
+#
+# (13) cardinality :=  required  repeated
+#
+$result = reset($tokens);
+
+        $result = "required repeated";
+    
+}
+
+function reduce_14_type_1($tokens, &$result) {
+#
+# (14) type :=  uint
+#
+$result = reset($tokens);
+
+        $result = array(
+            "type" => "uint",
+            "size" => $tokens[0]
+        );
+    
+}
+
+function reduce_15_type_2($tokens, &$result) {
+#
+# (15) type :=  data
+#
+$result = reset($tokens);
+
+        $result = array(
+            "type" => "data"
+        );
+    
+}
+
+function reduce_16_type_3($tokens, &$result) {
+#
+# (16) type :=  data  srpar  string  erpar
+#
+$result = reset($tokens);
+
+        $result = array(
+            "type" => "constdata",
+            "size" => $tokens[2]
+        );
+    
+}
+
+function reduce_17_type_4($tokens, &$result) {
+#
+# (17) type :=  message  name
+#
+$result = reset($tokens);
+
+        $result = array(
+            "type" => "message",
+            "message" => $tokens[1]
+        );
+    
+}
+
+function reduce_18_start_1($tokens, &$result) {
+#
+# (18) 'start' :=  file
+#
+$result = reset($tokens);
+
+}
+
+var $method = array (
+  0 => 'reduce_0_file_1',
+  1 => 'reduce_1_directives_1',
+  2 => 'reduce_2_directives_2',
+  3 => 'reduce_3_directive_1',
+  4 => 'reduce_4_messages_1',
+  5 => 'reduce_5_messages_2',
+  6 => 'reduce_6_msgspec_1',
+  7 => 'reduce_7_entries_1',
+  8 => 'reduce_8_entries_2',
+  9 => 'reduce_9_entry_1',
+  10 => 'reduce_10_cardinality_1',
+  11 => 'reduce_11_cardinality_2',
+  12 => 'reduce_12_cardinality_3',
+  13 => 'reduce_13_cardinality_4',
+  14 => 'reduce_14_type_1',
+  15 => 'reduce_15_type_2',
+  16 => 'reduce_16_type_3',
+  17 => 'reduce_17_type_4',
+  18 => 'reduce_18_start_1',
+);
+var $a = array (
+  0 => 
+  array (
+    'symbol' => 'file',
+    'len' => 2,
+    'replace' => true,
+  ),
+  1 => 
+  array (
+    'symbol' => 'directives',
+    'len' => 0,
+    'replace' => true,
+  ),
+  2 => 
+  array (
+    'symbol' => 'directives',
+    'len' => 3,
+    'replace' => true,
+  ),
+  3 => 
+  array (
+    'symbol' => 'directive',
+    'len' => 2,
+    'replace' => true,
+  ),
+  4 => 
+  array (
+    'symbol' => 'messages',
+    'len' => 1,
+    'replace' => true,
+  ),
+  5 => 
+  array (
+    'symbol' => 'messages',
+    'len' => 2,
+    'replace' => true,
+  ),
+  6 => 
+  array (
+    'symbol' => 'msgspec',
+    'len' => 6,
+    'replace' => true,
+  ),
+  7 => 
+  array (
+    'symbol' => 'entries',
+    'len' => 1,
+    'replace' => true,
+  ),
+  8 => 
+  array (
+    'symbol' => 'entries',
+    'len' => 2,
+    'replace' => true,
+  ),
+  9 => 
+  array (
+    'symbol' => 'entry',
+    'len' => 6,
+    'replace' => true,
+  ),
+  10 => 
+  array (
+    'symbol' => 'cardinality',
+    'len' => 1,
+    'replace' => true,
+  ),
+  11 => 
+  array (
+    'symbol' => 'cardinality',
+    'len' => 1,
+    'replace' => true,
+  ),
+  12 => 
+  array (
+    'symbol' => 'cardinality',
+    'len' => 1,
+    'replace' => true,
+  ),
+  13 => 
+  array (
+    'symbol' => 'cardinality',
+    'len' => 2,
+    'replace' => true,
+  ),
+  14 => 
+  array (
+    'symbol' => 'type',
+    'len' => 1,
+    'replace' => true,
+  ),
+  15 => 
+  array (
+    'symbol' => 'type',
+    'len' => 1,
+    'replace' => true,
+  ),
+  16 => 
+  array (
+    'symbol' => 'type',
+    'len' => 4,
+    'replace' => true,
+  ),
+  17 => 
+  array (
+    'symbol' => 'type',
+    'len' => 2,
+    'replace' => true,
+  ),
+  18 => 
+  array (
+    'symbol' => '\'start\'',
+    'len' => 1,
+    'replace' => true,
+  ),
+);
+}
diff --git a/external/badvpn_dns/bproto_generator/bproto.php b/external/badvpn_dns/bproto_generator/bproto.php
new file mode 100644
index 0000000..76f8c6e
--- /dev/null
+++ b/external/badvpn_dns/bproto_generator/bproto.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * @file bproto.php
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+require_once "lime/parse_engine.php";
+require_once "ProtoParser.php";
+require_once "bproto_functions.php";
+
+function assert_failure ($script, $line, $message)
+{
+    if ($message == "") {
+        fatal_error("Assertion failure at {$script}:{$line}");
+    } else {
+        fatal_error("Assertion failure at {$script}:{$line}: {$message}");
+    }
+}
+
+assert_options(ASSERT_CALLBACK, "assert_failure");
+
+function print_help ($name)
+{
+    echo <<<EOD
+Usage: {$name}
+    --name <string>             Output file prefix.
+    --input-file <file>         Message file to generate source for.
+    --output-dir <dir>          Destination directory for generated files.
+
+EOD;
+}
+
+$name = "";
+$input_file = "";
+$output_dir = "";
+
+for ($i = 1; $i < $argc;) {
+    $arg = $argv[$i++];
+    switch ($arg) {
+        case "--name":
+            $name = $argv[$i++];
+            break;
+        case "--input-file":
+            $input_file = $argv[$i++];
+            break;
+        case "--output-dir":
+            $output_dir = $argv[$i++];
+            break;
+        case "--help":
+            print_help($argv[0]);
+            exit(0);
+        default:
+            fatal_error("Unknown option: {$arg}");
+    }
+}
+
+if ($name == "") {
+    fatal_error("--name missing");
+}
+
+if ($input_file == "") {
+    fatal_error("--input-file missing");
+}
+
+if ($output_dir == "") {
+    fatal_error("--output-dir missing");
+}
+
+if (($data = file_get_contents($input_file)) === FALSE) {
+    fatal_error("Failed to read input file");
+}
+
+if (!tokenize($data, $tokens)) {
+    fatal_error("Failed to tokenize");
+}
+
+$parser = new parse_engine(new ProtoParser());
+
+try {
+    foreach ($tokens as $token) {
+        $parser->eat($token[0], $token[1]);
+    }
+    $parser->eat_eof();
+} catch (parse_error $e) {
+    fatal_error("$input_file: Parse error: ".$e->getMessage());
+}
+
+$data = generate_header($name, $parser->semantic["directives"], $parser->semantic["messages"]);
+if (file_put_contents("{$output_dir}/{$name}.h", $data) === NULL) {
+    fatal_error("{$input_file}: Failed to write .h file");
+}
diff --git a/external/badvpn_dns/bproto_generator/bproto_functions.php b/external/badvpn_dns/bproto_generator/bproto_functions.php
new file mode 100644
index 0000000..490c1bf
--- /dev/null
+++ b/external/badvpn_dns/bproto_generator/bproto_functions.php
@@ -0,0 +1,777 @@
+<?php
+
+function tokenize ($str, &$out) {
+    $out = array();
+
+    while (strlen($str) > 0) {
+        if (preg_match('/^\\/\\/.*/', $str, $matches)) {
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^\\s+/', $str, $matches)) {
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^include/', $str, $matches)) {
+            $out[] = array('include', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^message/', $str, $matches)) {
+            $out[] = array('message', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^repeated/', $str, $matches)) {
+            $out[] = array('repeated', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^required/', $str, $matches)) {
+            $out[] = array('required', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^optional/', $str, $matches)) {
+            $out[] = array('optional', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^{/', $str, $matches)) {
+            $out[] = array('spar', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^}/', $str, $matches)) {
+            $out[] = array('epar', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^\(/', $str, $matches)) {
+            $out[] = array('srpar', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^\)/', $str, $matches)) {
+            $out[] = array('erpar', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^=/', $str, $matches)) {
+            $out[] = array('equals', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^;/', $str, $matches)) {
+            $out[] = array('semicolon', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^uint(8|16|32|64)/', $str, $matches)) {
+            $out[] = array('uint', $matches[1]);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^data/', $str, $matches)) {
+            $out[] = array('data', null);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^[0-9]+/', $str, $matches)) {
+            $out[] = array('number', $matches[0]);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*/', $str, $matches)) {
+            $out[] = array('name', $matches[0]);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else if (preg_match('/^"([^"]*)"/', $str, $matches)) {
+            $out[] = array('string', $matches[1]);
+            $str = substr($str, strlen($matches[0]));
+        }
+        else {
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+function fatal_error ($message)
+{
+    fwrite(STDERR, "Fatal error: $message\n");
+
+    ob_get_clean();
+    exit(1);
+}
+
+function make_writer_decl ($msg, $entry)
+{
+    switch ($entry["type"]["type"]) {
+        case "uint":
+            return "void {$msg["name"]}Writer_Add{$entry["name"]} ({$msg["name"]}Writer *o, uint{$entry["type"]["size"]}_t v)";
+        case "data":
+            return "uint8_t * {$msg["name"]}Writer_Add{$entry["name"]} ({$msg["name"]}Writer *o, int len)";
+        case "constdata":
+            return "uint8_t * {$msg["name"]}Writer_Add{$entry["name"]} ({$msg["name"]}Writer *o)";
+        default:
+            assert(0);
+    }
+}
+
+function make_parser_decl ($msg, $entry)
+{
+    switch ($entry["type"]["type"]) {
+        case "uint":
+            return "int {$msg["name"]}Parser_Get{$entry["name"]} ({$msg["name"]}Parser *o, uint{$entry["type"]["size"]}_t *v)";
+        case "data":
+            return "int {$msg["name"]}Parser_Get{$entry["name"]} ({$msg["name"]}Parser *o, uint8_t **data, int *data_len)";
+        case "constdata":
+            return "int {$msg["name"]}Parser_Get{$entry["name"]} ({$msg["name"]}Parser *o, uint8_t **data)";
+        default:
+            assert(0);
+    }
+}
+
+function make_parser_reset_decl ($msg, $entry)
+{
+    return "void {$msg["name"]}Parser_Reset{$entry["name"]} ({$msg["name"]}Parser *o)";
+}
+
+function make_parser_forward_decl ($msg, $entry)
+{
+    return "void {$msg["name"]}Parser_Forward{$entry["name"]} ({$msg["name"]}Parser *o)";
+}
+
+function make_type_name ($msg, $entry)
+{
+    switch ($entry["type"]["type"]) {
+        case "uint":
+            return "BPROTO_TYPE_UINT{$entry["type"]["size"]}";
+        case "data":
+            return "BPROTO_TYPE_DATA";
+        case "constdata":
+            return "BPROTO_TYPE_CONSTDATA";
+        default:
+            assert(0);
+    }
+}
+
+function make_finish_assert ($msg, $entry)
+{
+    switch ($entry["cardinality"]) {
+        case "repeated":
+            return "ASSERT(o->{$entry["name"]}_count >= 0)";
+        case "required repeated":
+            return "ASSERT(o->{$entry["name"]}_count >= 1)";
+        case "optional":
+            return "ASSERT(o->{$entry["name"]}_count >= 0 && o->{$entry["name"]}_count <= 1)";
+        case "required":
+            return "ASSERT(o->{$entry["name"]}_count == 1)";
+        default:
+            assert(0);
+    }
+}
+
+function make_add_count_assert ($msg, $entry)
+{
+    if (in_array($entry["cardinality"], array("optional", "required"))) {
+        return "ASSERT(o->{$entry["name"]}_count == 0)";
+    }
+    return "";
+}
+
+function make_add_length_assert ($msg, $entry)
+{
+    if ($entry["type"]["type"] == "data") {
+        return "ASSERT(len >= 0 && len <= UINT32_MAX)";
+    }
+    return "";
+}
+
+function make_size_define ($msg, $entry)
+{
+    switch ($entry["type"]["type"]) {
+        case "uint":
+            return "#define {$msg["name"]}_SIZE{$entry["name"]} (sizeof(struct BProto_header_s) + sizeof(struct BProto_uint{$entry["type"]["size"]}_s))";
+        case "data":
+            return "#define {$msg["name"]}_SIZE{$entry["name"]}(_len) (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + (_len))";
+        case "constdata":
+            return "#define {$msg["name"]}_SIZE{$entry["name"]} (sizeof(struct BProto_header_s) + sizeof(struct BProto_data_header_s) + ({$entry["type"]["size"]}))";
+        default:
+            assert(0);
+    }
+}
+
+function generate_header ($name, $directives, $messages) {
+    ob_start();
+
+    echo <<<EOD
+/*
+    DO NOT EDIT THIS FILE!
+    This file was automatically generated by the bproto generator.
+*/
+
+#include <stdint.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <bproto/BProto.h>
+
+
+EOD;
+
+    foreach ($directives as $directive) {
+        if ($directive["type"] == "include") {
+            echo <<<EOD
+#include "{$directive["file"]}"
+
+EOD;
+        }
+    }
+
+    echo <<<EOD
+
+
+EOD;
+
+    foreach ($messages as $msg) {
+
+        foreach ($msg["entries"] as $entry) {
+            $def = make_size_define($msg, $entry);
+            echo <<<EOD
+{$def}
+
+EOD;
+        }
+
+        echo <<<EOD
+
+typedef struct {
+    uint8_t *out;
+    int used;
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    int {$entry["name"]}_count;
+
+EOD;
+        }
+
+        echo <<<EOD
+} {$msg["name"]}Writer;
+
+static void {$msg["name"]}Writer_Init ({$msg["name"]}Writer *o, uint8_t *out);
+static int {$msg["name"]}Writer_Finish ({$msg["name"]}Writer *o);
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $decl = make_writer_decl($msg, $entry);
+            echo <<<EOD
+static {$decl};
+
+EOD;
+        }
+
+        echo <<<EOD
+
+typedef struct {
+    uint8_t *buf;
+    int buf_len;
+
+EOD;
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    int {$entry["name"]}_start;
+    int {$entry["name"]}_span;
+    int {$entry["name"]}_pos;
+
+EOD;
+        }
+
+        echo <<<EOD
+} {$msg["name"]}Parser;
+
+static int {$msg["name"]}Parser_Init ({$msg["name"]}Parser *o, uint8_t *buf, int buf_len);
+static int {$msg["name"]}Parser_GotEverything ({$msg["name"]}Parser *o);
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $decl = make_parser_decl($msg, $entry);
+            $reset_decl = make_parser_reset_decl($msg, $entry);
+            $forward_decl = make_parser_forward_decl($msg, $entry);
+            echo <<<EOD
+static {$decl};
+static {$reset_decl};
+static {$forward_decl};
+
+EOD;
+        }
+
+        echo <<<EOD
+
+void {$msg["name"]}Writer_Init ({$msg["name"]}Writer *o, uint8_t *out)
+{
+    o->out = out;
+    o->used = 0;
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    o->{$entry["name"]}_count = 0;
+
+EOD;
+        }
+
+        echo <<<EOD
+}
+
+int {$msg["name"]}Writer_Finish ({$msg["name"]}Writer *o)
+{
+    ASSERT(o->used >= 0)
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $ass = make_finish_assert($msg, $entry);
+            echo <<<EOD
+    {$ass}
+
+EOD;
+        }
+
+        echo <<<EOD
+
+    return o->used;
+}
+
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $decl = make_writer_decl($msg, $entry);
+            $type = make_type_name($msg, $entry);
+            $add_count_assert = make_add_count_assert($msg, $entry);
+            $add_length_assert = make_add_length_assert($msg, $entry);
+
+            echo <<<EOD
+{$decl}
+{
+    ASSERT(o->used >= 0)
+    {$add_count_assert}
+    {$add_length_assert}
+
+    struct BProto_header_s header;
+    header.id = htol16({$entry["id"]});
+    header.type = htol16({$type});
+    memcpy(o->out + o->used, &header, sizeof(header));
+    o->used += sizeof(struct BProto_header_s);
+
+
+EOD;
+            switch ($entry["type"]["type"]) {
+                case "uint":
+                    echo <<<EOD
+    struct BProto_uint{$entry["type"]["size"]}_s data;
+    data.v = htol{$entry["type"]["size"]}(v);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_uint{$entry["type"]["size"]}_s);
+
+EOD;
+                    break;
+                case "data":
+                    echo <<<EOD
+    struct BProto_data_header_s data;
+    data.len = htol32(len);
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_data_header_s);
+
+    uint8_t *dest = (o->out + o->used);
+    o->used += len;
+
+EOD;
+                    break;
+                case "constdata":
+                    echo <<<EOD
+    struct BProto_data_header_s data;
+    data.len = htol32({$entry["type"]["size"]});
+    memcpy(o->out + o->used, &data, sizeof(data));
+    o->used += sizeof(struct BProto_data_header_s);
+
+    uint8_t *dest = (o->out + o->used);
+    o->used += ({$entry["type"]["size"]});
+
+EOD;
+                    break;
+                default:
+                    assert(0);
+            }
+
+            echo <<<EOD
+
+    o->{$entry["name"]}_count++;
+
+EOD;
+            if (in_array($entry["type"]["type"], array("data", "constdata"))) {
+                echo <<<EOD
+
+    return dest;
+
+EOD;
+            }
+
+            echo <<<EOD
+}
+
+
+EOD;
+        }
+
+        echo <<<EOD
+int {$msg["name"]}Parser_Init ({$msg["name"]}Parser *o, uint8_t *buf, int buf_len)
+{
+    ASSERT(buf_len >= 0)
+
+    o->buf = buf;
+    o->buf_len = buf_len;
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    o->{$entry["name"]}_start = o->buf_len;
+    o->{$entry["name"]}_span = 0;
+    o->{$entry["name"]}_pos = 0;
+
+EOD;
+        }
+
+        echo <<<EOD
+
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            echo <<<EOD
+    int {$entry["name"]}_count = 0;
+
+EOD;
+        }
+
+        echo <<<EOD
+
+    int pos = 0;
+    int left = o->buf_len;
+
+    while (left > 0) {
+        int entry_pos = pos;
+
+        if (!(left >= sizeof(struct BProto_header_s))) {
+            return 0;
+        }
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + pos, sizeof(header));
+        pos += sizeof(struct BProto_header_s);
+        left -= sizeof(struct BProto_header_s);
+        uint16_t type = ltoh16(header.type);
+        uint16_t id = ltoh16(header.id);
+
+        switch (type) {
+
+EOD;
+
+        foreach (array(8, 16, 32, 64) as $bits) {
+            echo <<<EOD
+            case BPROTO_TYPE_UINT{$bits}: {
+                if (!(left >= sizeof(struct BProto_uint{$bits}_s))) {
+                    return 0;
+                }
+                pos += sizeof(struct BProto_uint{$bits}_s);
+                left -= sizeof(struct BProto_uint{$bits}_s);
+
+                switch (id) {
+
+EOD;
+
+            foreach ($msg["entries"] as $entry) {
+                if (!($entry["type"]["type"] == "uint" && $entry["type"]["size"] == $bits)) {
+                    continue;
+                }
+                $type = make_type_name($msg, $entry);
+                echo <<<EOD
+                    case {$entry["id"]}:
+                        if (o->{$entry["name"]}_start == o->buf_len) {
+                            o->{$entry["name"]}_start = entry_pos;
+                        }
+                        o->{$entry["name"]}_span = pos - o->{$entry["name"]}_start;
+                        {$entry["name"]}_count++;
+                        break;
+
+EOD;
+            }
+
+            echo <<<EOD
+                    default:
+                        return 0;
+                }
+            } break;
+
+EOD;
+        }
+
+        echo <<<EOD
+            case BPROTO_TYPE_DATA:
+            case BPROTO_TYPE_CONSTDATA:
+            {
+                if (!(left >= sizeof(struct BProto_data_header_s))) {
+                    return 0;
+                }
+                struct BProto_data_header_s val;
+                memcpy(&val, o->buf + pos, sizeof(val));
+                pos += sizeof(struct BProto_data_header_s);
+                left -= sizeof(struct BProto_data_header_s);
+
+                uint32_t payload_len = ltoh32(val.len);
+                if (!(left >= payload_len)) {
+                    return 0;
+                }
+                pos += payload_len;
+                left -= payload_len;
+
+                switch (id) {
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            if (!in_array($entry["type"]["type"], array("data", "constdata"))) {
+                continue;
+            }
+            $type = make_type_name($msg, $entry);
+            echo <<<EOD
+                    case {$entry["id"]}:
+                        if (!(type == {$type})) {
+                            return 0;
+                        }
+
+EOD;
+            if ($entry["type"]["type"] == "constdata") {
+                echo <<<EOD
+                        if (!(payload_len == ({$entry["type"]["size"]}))) {
+                            return 0;
+                        }
+
+EOD;
+            }
+            echo <<<EOD
+                        if (o->{$entry["name"]}_start == o->buf_len) {
+                            o->{$entry["name"]}_start = entry_pos;
+                        }
+                        o->{$entry["name"]}_span = pos - o->{$entry["name"]}_start;
+                        {$entry["name"]}_count++;
+                        break;
+
+EOD;
+        }
+
+        echo <<<EOD
+                    default:
+                        return 0;
+                }
+            } break;
+            default:
+                return 0;
+        }
+    }
+
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $cond = "";
+            switch ($entry["cardinality"]) {
+                case "repeated":
+                    break;
+                case "required repeated":
+                    $cond = "{$entry["name"]}_count >= 1";
+                    break;
+                case "optional":
+                    $cond = "{$entry["name"]}_count <= 1";
+                    break;
+                case "required":
+                    $cond = "{$entry["name"]}_count == 1";
+                    break;
+                default:
+                    assert(0);
+            }
+            if ($cond) {
+                echo <<<EOD
+    if (!({$cond})) {
+        return 0;
+    }
+
+EOD;
+            }
+        }
+
+        echo <<<EOD
+
+    return 1;
+}
+
+int {$msg["name"]}Parser_GotEverything ({$msg["name"]}Parser *o)
+{
+    return (
+
+EOD;
+
+        $first = 1;
+        foreach ($msg["entries"] as $entry) {
+            if ($first) {
+                $first = 0;
+            } else {
+                echo <<<EOD
+        &&
+
+EOD;
+            }
+            echo <<<EOD
+        o->{$entry["name"]}_pos == o->{$entry["name"]}_span
+
+EOD;
+        }
+    
+
+        echo <<<EOD
+    );
+}
+
+
+EOD;
+
+        foreach ($msg["entries"] as $entry) {
+            $decl = make_parser_decl($msg, $entry);
+            $reset_decl = make_parser_reset_decl($msg, $entry);
+            $forward_decl = make_parser_forward_decl($msg, $entry);
+            $type = make_type_name($msg, $entry);
+
+            echo <<<EOD
+{$decl}
+{
+    ASSERT(o->{$entry["name"]}_pos >= 0)
+    ASSERT(o->{$entry["name"]}_pos <= o->{$entry["name"]}_span)
+
+    int left = o->{$entry["name"]}_span - o->{$entry["name"]}_pos;
+
+    while (left > 0) {
+        ASSERT(left >= sizeof(struct BProto_header_s))
+        struct BProto_header_s header;
+        memcpy(&header, o->buf + o->{$entry["name"]}_start + o->{$entry["name"]}_pos, sizeof(header));
+        o->{$entry["name"]}_pos += sizeof(struct BProto_header_s);
+        left -= sizeof(struct BProto_header_s);
+        uint16_t type = ltoh16(header.type);
+        uint16_t id = ltoh16(header.id);
+
+        switch (type) {
+
+EOD;
+
+            foreach (array(8, 16, 32, 64) as $bits) {
+                echo <<<EOD
+            case BPROTO_TYPE_UINT{$bits}: {
+                ASSERT(left >= sizeof(struct BProto_uint{$bits}_s))
+
+EOD;
+                if ($entry["type"]["type"] == "uint" && $entry["type"]["size"] == $bits) {
+                    echo <<<EOD
+                struct BProto_uint{$bits}_s val;
+                memcpy(&val, o->buf + o->{$entry["name"]}_start + o->{$entry["name"]}_pos, sizeof(val));
+
+EOD;
+                }
+                echo <<<EOD
+                o->{$entry["name"]}_pos += sizeof(struct BProto_uint{$bits}_s);
+                left -= sizeof(struct BProto_uint{$bits}_s);
+
+EOD;
+                if ($entry["type"]["type"] == "uint" && $entry["type"]["size"] == $bits) {
+                    echo <<<EOD
+
+                if (id == {$entry["id"]}) {
+                    *v = ltoh{$bits}(val.v);
+                    return 1;
+                }
+
+EOD;
+                }
+
+                echo <<<EOD
+            } break;
+
+EOD;
+            }
+
+            echo <<<EOD
+            case BPROTO_TYPE_DATA:
+            case BPROTO_TYPE_CONSTDATA:
+            {
+                ASSERT(left >= sizeof(struct BProto_data_header_s))
+                struct BProto_data_header_s val;
+                memcpy(&val, o->buf + o->{$entry["name"]}_start + o->{$entry["name"]}_pos, sizeof(val));
+                o->{$entry["name"]}_pos += sizeof(struct BProto_data_header_s);
+                left -= sizeof(struct BProto_data_header_s);
+
+                uint32_t payload_len = ltoh32(val.len);
+                ASSERT(left >= payload_len)
+
+EOD;
+            if ($entry["type"]["type"] == "data" || $entry["type"]["type"] == "constdata") {
+                    echo <<<EOD
+                uint8_t *payload = o->buf + o->{$entry["name"]}_start + o->{$entry["name"]}_pos;
+
+EOD;
+            }
+            echo <<<EOD
+                o->{$entry["name"]}_pos += payload_len;
+                left -= payload_len;
+
+EOD;
+            if ($entry["type"]["type"] == "data") {
+                echo <<<EOD
+
+                if (type == BPROTO_TYPE_DATA && id == {$entry["id"]}) {
+                    *data = payload;
+                    *data_len = payload_len;
+                    return 1;
+                }
+
+EOD;
+            }
+            else if ($entry["type"]["type"] == "constdata") {
+                echo <<<EOD
+
+                if (type == BPROTO_TYPE_CONSTDATA && id == {$entry["id"]}) {
+                    *data = payload;
+                    return 1;
+                }
+
+EOD;
+            }
+
+            echo <<<EOD
+            } break;
+            default:
+                ASSERT(0);
+        }
+    }
+
+    return 0;
+}
+
+{$reset_decl}
+{
+    o->{$entry["name"]}_pos = 0;
+}
+
+{$forward_decl}
+{
+    o->{$entry["name"]}_pos = o->{$entry["name"]}_span;
+}
+
+
+EOD;
+        }
+    }
+
+    return ob_get_clean();
+}
diff --git a/external/badvpn_dns/client/CMakeLists.txt b/external/badvpn_dns/client/CMakeLists.txt
new file mode 100644
index 0000000..3cec1a9
--- /dev/null
+++ b/external/badvpn_dns/client/CMakeLists.txt
@@ -0,0 +1,30 @@
+add_executable(badvpn-client
+    client.c
+    StreamPeerIO.c
+    DatagramPeerIO.c
+    PasswordListener.c
+    DataProto.c
+    FrameDecider.c
+    DPRelay.c
+    DPReceive.c
+    FragmentProtoDisassembler.c
+    FragmentProtoAssembler.c
+    SPProtoEncoder.c
+    SPProtoDecoder.c
+    DataProtoKeepaliveSource.c
+    PeerChat.c
+    SCOutmsgEncoder.c
+    SimpleStreamBuffer.c
+    SinglePacketSource.c
+)
+target_link_libraries(badvpn-client system flow flowextra tuntap server_conection security threadwork ${NSPR_LIBRARIES} ${NSS_LIBRARIES})
+
+install(
+    TARGETS badvpn-client
+    RUNTIME DESTINATION bin
+)
+
+install(
+    FILES badvpn-client.8
+    DESTINATION share/man/man8
+)
diff --git a/external/badvpn_dns/client/DPReceive.c b/external/badvpn_dns/client/DPReceive.c
new file mode 100644
index 0000000..da70c74
--- /dev/null
+++ b/external/badvpn_dns/client/DPReceive.c
@@ -0,0 +1,324 @@
+/**
+ * @file DPReceive.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <limits.h>
+#include <string.h>
+
+#include <protocol/dataproto.h>
+#include <misc/byteorder.h>
+#include <misc/offset.h>
+#include <base/BLog.h>
+
+#include <client/DPReceive.h>
+
+#include <generated/blog_channel_DPReceive.h>
+
+static DPReceivePeer * find_peer (DPReceiveDevice *o, peerid_t id)
+{
+    for (LinkedList1Node *node = LinkedList1_GetFirst(&o->peers_list); node; node = LinkedList1Node_Next(node)) {
+        DPReceivePeer *p = UPPER_OBJECT(node, DPReceivePeer, list_node);
+        if (p->peer_id == id) {
+            return p;
+        }
+    }
+    
+    return NULL;
+}
+
+static void receiver_recv_handler_send (DPReceiveReceiver *o, uint8_t *packet, int packet_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DPReceivePeer *peer = o->peer;
+    DPReceiveDevice *device = peer->device;
+    ASSERT(packet_len >= 0)
+    ASSERT(packet_len <= device->packet_mtu)
+    
+    uint8_t *data = packet;
+    int data_len = packet_len;
+    
+    int local = 0;
+    DPReceivePeer *src_peer;
+    DPReceivePeer *relay_dest_peer = NULL;
+    
+    // check header
+    if (data_len < sizeof(struct dataproto_header)) {
+        BLog(BLOG_WARNING, "no dataproto header");
+        goto out;
+    }
+    struct dataproto_header header;
+    memcpy(&header, data, sizeof(header));
+    data += sizeof(header);
+    data_len -= sizeof(header);
+    uint8_t flags = ltoh8(header.flags);
+    peerid_t from_id = ltoh16(header.from_id);
+    int num_ids = ltoh16(header.num_peer_ids);
+    
+    // check destination ID
+    if (!(num_ids == 0 || num_ids == 1)) {
+        BLog(BLOG_WARNING, "wrong number of destinations");
+        goto out;
+    }
+    peerid_t to_id = 0; // to remove warning
+    if (num_ids == 1) {
+        if (data_len < sizeof(struct dataproto_peer_id)) {
+            BLog(BLOG_WARNING, "missing destination");
+            goto out;
+        }
+        struct dataproto_peer_id id;
+        memcpy(&id, data, sizeof(id));
+        to_id = ltoh16(id.id);
+        data += sizeof(id);
+        data_len -= sizeof(id);
+    }
+    
+    // check remaining data
+    if (data_len > device->device_mtu) {
+        BLog(BLOG_WARNING, "frame too large");
+        goto out;
+    }
+    
+    // inform sink of received packet
+    if (peer->dp_sink) {
+        DataProtoSink_Received(peer->dp_sink, !!(flags & DATAPROTO_FLAGS_RECEIVING_KEEPALIVES));
+    }
+    
+    if (num_ids == 1) {
+        // find source peer
+        if (!(src_peer = find_peer(device, from_id))) {
+            BLog(BLOG_INFO, "source peer %d not known", (int)from_id);
+            goto out;
+        }
+        
+        // is frame for device or another peer?
+        if (device->have_peer_id && to_id == device->peer_id) {
+            // let the frame decider analyze the frame
+            FrameDeciderPeer_Analyze(src_peer->decider_peer, data, data_len);
+            
+            // pass frame to device
+            local = 1;
+        } else {
+            // check if relaying is allowed
+            if (!peer->is_relay_client) {
+                BLog(BLOG_WARNING, "relaying not allowed");
+                goto out;
+            }
+            
+            // provided source ID must be the peer sending the frame
+            if (src_peer != peer) {
+                BLog(BLOG_WARNING, "relay source must be the sending peer");
+                goto out;
+            }
+            
+            // find destination peer
+            DPReceivePeer *dest_peer = find_peer(device, to_id);
+            if (!dest_peer) {
+                BLog(BLOG_INFO, "relay destination peer not known");
+                goto out;
+            }
+            
+            // destination cannot be source
+            if (dest_peer == src_peer) {
+                BLog(BLOG_WARNING, "relay destination cannot be the source");
+                goto out;
+            }
+            
+            relay_dest_peer = dest_peer;
+        }
+    }
+    
+out:
+    // accept packet
+    PacketPassInterface_Done(&o->recv_if);
+    
+    // pass packet to device
+    if (local) {
+        o->device->output_func(o->device->output_func_user, data, data_len);
+    }
+    
+    // relay frame
+    if (relay_dest_peer) {
+        DPRelayRouter_SubmitFrame(&device->relay_router, &src_peer->relay_source, &relay_dest_peer->relay_sink, data, data_len, device->relay_flow_buffer_size, device->relay_flow_inactivity_time);
+    }
+}
+
+int DPReceiveDevice_Init (DPReceiveDevice *o, int device_mtu, DPReceiveDevice_output_func output_func, void *output_func_user, BReactor *reactor, int relay_flow_buffer_size, int relay_flow_inactivity_time)
+{
+    ASSERT(device_mtu >= 0)
+    ASSERT(device_mtu <= INT_MAX - DATAPROTO_MAX_OVERHEAD)
+    ASSERT(output_func)
+    ASSERT(relay_flow_buffer_size > 0)
+    
+    // init arguments
+    o->device_mtu = device_mtu;
+    o->output_func = output_func;
+    o->output_func_user = output_func_user;
+    o->reactor = reactor;
+    o->relay_flow_buffer_size = relay_flow_buffer_size;
+    o->relay_flow_inactivity_time = relay_flow_inactivity_time;
+    
+    // remember packet MTU
+    o->packet_mtu = DATAPROTO_MAX_OVERHEAD + o->device_mtu;
+    
+    // init relay router
+    if (!DPRelayRouter_Init(&o->relay_router, o->device_mtu, o->reactor)) {
+        BLog(BLOG_ERROR, "DPRelayRouter_Init failed");
+        goto fail0;
+    }
+    
+    // have no peer ID
+    o->have_peer_id = 0;
+    
+    // init peers list
+    LinkedList1_Init(&o->peers_list);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void DPReceiveDevice_Free (DPReceiveDevice *o)
+{
+    DebugObject_Free(&o->d_obj);
+    ASSERT(LinkedList1_IsEmpty(&o->peers_list))
+    
+    // free relay router
+    DPRelayRouter_Free(&o->relay_router);
+}
+
+void DPReceiveDevice_SetPeerID (DPReceiveDevice *o, peerid_t peer_id)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // remember peer ID
+    o->peer_id = peer_id;
+    o->have_peer_id = 1;
+}
+
+void DPReceivePeer_Init (DPReceivePeer *o, DPReceiveDevice *device, peerid_t peer_id, FrameDeciderPeer *decider_peer, int is_relay_client)
+{
+    DebugObject_Access(&device->d_obj);
+    ASSERT(is_relay_client == 0 || is_relay_client == 1)
+    
+    // init arguments
+    o->device = device;
+    o->peer_id = peer_id;
+    o->decider_peer = decider_peer;
+    o->is_relay_client = is_relay_client;
+    
+    // init relay source
+    DPRelaySource_Init(&o->relay_source, &device->relay_router, o->peer_id, device->reactor);
+    
+    // init relay sink
+    DPRelaySink_Init(&o->relay_sink, o->peer_id);
+    
+    // have no sink
+    o->dp_sink = NULL;
+    
+    // insert to peers list
+    LinkedList1_Append(&device->peers_list, &o->list_node);
+    
+    DebugCounter_Init(&o->d_receivers_ctr);
+    DebugObject_Init(&o->d_obj);
+}
+
+void DPReceivePeer_Free (DPReceivePeer *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Free(&o->d_receivers_ctr);
+    ASSERT(!o->dp_sink)
+    
+    // remove from peers list
+    LinkedList1_Remove(&o->device->peers_list, &o->list_node);
+    
+    // free relay sink
+    DPRelaySink_Free(&o->relay_sink);
+    
+    // free relay source
+    DPRelaySource_Free(&o->relay_source);
+}
+
+void DPReceivePeer_AttachSink (DPReceivePeer *o, DataProtoSink *dp_sink)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->dp_sink)
+    ASSERT(dp_sink)
+    
+    // attach relay sink
+    DPRelaySink_Attach(&o->relay_sink, dp_sink);
+    
+    o->dp_sink = dp_sink;
+}
+
+void DPReceivePeer_DetachSink (DPReceivePeer *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->dp_sink)
+    
+    // detach relay sink
+    DPRelaySink_Detach(&o->relay_sink);
+    
+    o->dp_sink = NULL;
+}
+
+void DPReceiveReceiver_Init (DPReceiveReceiver *o, DPReceivePeer *peer)
+{
+    DebugObject_Access(&peer->d_obj);
+    DPReceiveDevice *device = peer->device;
+    
+    // init arguments
+    o->peer = peer;
+    
+    // remember device
+    o->device = device;
+    
+    // init receive interface
+    PacketPassInterface_Init(&o->recv_if, device->packet_mtu, (PacketPassInterface_handler_send)receiver_recv_handler_send, o, BReactor_PendingGroup(device->reactor));
+    
+    DebugCounter_Increment(&peer->d_receivers_ctr);
+    DebugObject_Init(&o->d_obj);
+}
+
+void DPReceiveReceiver_Free (DPReceiveReceiver *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Decrement(&o->peer->d_receivers_ctr);
+    
+    // free receive interface
+    PacketPassInterface_Free(&o->recv_if);
+}
+
+PacketPassInterface * DPReceiveReceiver_GetInput (DPReceiveReceiver *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->recv_if;
+}
diff --git a/external/badvpn_dns/client/DPReceive.h b/external/badvpn_dns/client/DPReceive.h
new file mode 100644
index 0000000..ecc5a05
--- /dev/null
+++ b/external/badvpn_dns/client/DPReceive.h
@@ -0,0 +1,98 @@
+/**
+ * @file DPReceive.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Receive processing for the VPN client.
+ */
+
+#ifndef BADVPN_CLIENT_DPRECEIVE_H
+#define BADVPN_CLIENT_DPRECEIVE_H
+
+#include <protocol/scproto.h>
+#include <misc/debugcounter.h>
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <client/DataProto.h>
+#include <client/DPRelay.h>
+#include <client/FrameDecider.h>
+
+typedef void (*DPReceiveDevice_output_func) (void *output_user, uint8_t *data, int data_len);
+
+struct DPReceiveReceiver_s;
+
+typedef struct {
+    int device_mtu;
+    DPReceiveDevice_output_func output_func;
+    void *output_func_user;
+    BReactor *reactor;
+    int relay_flow_buffer_size;
+    int relay_flow_inactivity_time;
+    int packet_mtu;
+    DPRelayRouter relay_router;
+    int have_peer_id;
+    peerid_t peer_id;
+    LinkedList1 peers_list;
+    DebugObject d_obj;
+} DPReceiveDevice;
+
+typedef struct {
+    DPReceiveDevice *device;
+    peerid_t peer_id;
+    FrameDeciderPeer *decider_peer;
+    int is_relay_client;
+    DPRelaySource relay_source;
+    DPRelaySink relay_sink;
+    DataProtoSink *dp_sink;
+    LinkedList1Node list_node;
+    DebugObject d_obj;
+    DebugCounter d_receivers_ctr;
+} DPReceivePeer;
+
+typedef struct DPReceiveReceiver_s {
+    DPReceivePeer *peer;
+    DPReceiveDevice *device;
+    PacketPassInterface recv_if;
+    DebugObject d_obj;
+} DPReceiveReceiver;
+
+int DPReceiveDevice_Init (DPReceiveDevice *o, int device_mtu, DPReceiveDevice_output_func output_func, void *output_func_user, BReactor *reactor, int relay_flow_buffer_size, int relay_flow_inactivity_time) WARN_UNUSED;
+void DPReceiveDevice_Free (DPReceiveDevice *o);
+void DPReceiveDevice_SetPeerID (DPReceiveDevice *o, peerid_t peer_id);
+
+void DPReceivePeer_Init (DPReceivePeer *o, DPReceiveDevice *device, peerid_t peer_id, FrameDeciderPeer *decider_peer, int is_relay_client);
+void DPReceivePeer_Free (DPReceivePeer *o);
+void DPReceivePeer_AttachSink (DPReceivePeer *o, DataProtoSink *dp_sink);
+void DPReceivePeer_DetachSink (DPReceivePeer *o);
+
+void DPReceiveReceiver_Init (DPReceiveReceiver *o, DPReceivePeer *peer);
+void DPReceiveReceiver_Free (DPReceiveReceiver *o);
+PacketPassInterface * DPReceiveReceiver_GetInput (DPReceiveReceiver *o);
+
+#endif
diff --git a/external/badvpn_dns/client/DPRelay.c b/external/badvpn_dns/client/DPRelay.c
new file mode 100644
index 0000000..983e3ad
--- /dev/null
+++ b/external/badvpn_dns/client/DPRelay.c
@@ -0,0 +1,307 @@
+/**
+ * @file DPRelay.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <base/BLog.h>
+
+#include <client/DPRelay.h>
+
+#include <generated/blog_channel_DPRelay.h>
+
+static void flow_inactivity_handler (struct DPRelay_flow *flow);
+
+static struct DPRelay_flow * create_flow (DPRelaySource *src, DPRelaySink *sink, int num_packets, int inactivity_time)
+{
+    ASSERT(num_packets > 0)
+    
+    // allocate structure
+    struct DPRelay_flow *flow = (struct DPRelay_flow *)malloc(sizeof(*flow));
+    if (!flow) {
+        BLog(BLOG_ERROR, "relay flow %d->%d: malloc failed", (int)src->source_id, (int)sink->dest_id);
+        goto fail0;
+    }
+    
+    // set src and sink
+    flow->src = src;
+    flow->sink = sink;
+    
+    // init DataProtoFlow
+    if (!DataProtoFlow_Init(&flow->dp_flow, &src->router->dp_source, src->source_id, sink->dest_id, num_packets, inactivity_time, flow, (DataProtoFlow_handler_inactivity)flow_inactivity_handler)) {
+        BLog(BLOG_ERROR, "relay flow %d->%d: DataProtoFlow_Init failed", (int)src->source_id, (int)sink->dest_id);
+        goto fail1;
+    }
+    
+    // insert to source list
+    LinkedList1_Append(&src->flows_list, &flow->src_list_node);
+    
+    // insert to sink list
+    LinkedList1_Append(&sink->flows_list, &flow->sink_list_node);
+    
+    // attach flow if needed
+    if (sink->dp_sink) {
+        DataProtoFlow_Attach(&flow->dp_flow, sink->dp_sink);
+    }
+    
+    BLog(BLOG_INFO, "relay flow %d->%d: created", (int)src->source_id, (int)sink->dest_id);
+    
+    return flow;
+    
+fail1:
+    free(flow);
+fail0:
+    return NULL;
+}
+
+static void free_flow (struct DPRelay_flow *flow)
+{
+    // detach flow if needed
+    if (flow->sink->dp_sink) {
+        DataProtoFlow_Detach(&flow->dp_flow);
+    }
+    
+    // remove posible router reference
+    if (flow->src->router->current_flow == flow) {
+        flow->src->router->current_flow = NULL;
+    }
+    
+    // remove from sink list
+    LinkedList1_Remove(&flow->sink->flows_list, &flow->sink_list_node);
+    
+    // remove from source list
+    LinkedList1_Remove(&flow->src->flows_list, &flow->src_list_node);
+    
+    // free DataProtoFlow
+    DataProtoFlow_Free(&flow->dp_flow);
+    
+    // free structore
+    free(flow);
+}
+
+static void flow_inactivity_handler (struct DPRelay_flow *flow)
+{
+    BLog(BLOG_INFO, "relay flow %d->%d: timed out", (int)flow->src->source_id, (int)flow->sink->dest_id);
+    
+    free_flow(flow);
+}
+
+static struct DPRelay_flow * source_find_flow (DPRelaySource *o, DPRelaySink *sink)
+{
+    for (LinkedList1Node *node = LinkedList1_GetFirst(&o->flows_list); node; node = LinkedList1Node_Next(node)) {
+        struct DPRelay_flow *flow = UPPER_OBJECT(node, struct DPRelay_flow, src_list_node);
+        ASSERT(flow->src == o)
+        if (flow->sink == sink) {
+            return flow;
+        }
+    }
+    
+    return NULL;
+}
+
+static void router_dp_source_handler (DPRelayRouter *o, const uint8_t *frame, int frame_len)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (!o->current_flow) {
+        return;
+    }
+    
+    // route frame to current flow
+    DataProtoFlow_Route(&o->current_flow->dp_flow, 0);
+    
+    // set no current flow
+    o->current_flow = NULL;
+}
+
+int DPRelayRouter_Init (DPRelayRouter *o, int frame_mtu, BReactor *reactor)
+{
+    ASSERT(frame_mtu >= 0)
+    ASSERT(frame_mtu <= INT_MAX - DATAPROTO_MAX_OVERHEAD)
+    
+    // init arguments
+    o->frame_mtu = frame_mtu;
+    
+    // init BufferWriter
+    BufferWriter_Init(&o->writer, frame_mtu, BReactor_PendingGroup(reactor));
+    
+    // init DataProtoSource
+    if (!DataProtoSource_Init(&o->dp_source, BufferWriter_GetOutput(&o->writer), (DataProtoSource_handler)router_dp_source_handler, o, reactor)) {
+        BLog(BLOG_ERROR, "DataProtoSource_Init failed");
+        goto fail1;
+    }
+    
+    // have no current flow
+    o->current_flow = NULL;
+    
+    DebugCounter_Init(&o->d_ctr);
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    BufferWriter_Free(&o->writer);
+    return 0;
+}
+
+void DPRelayRouter_Free (DPRelayRouter *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Free(&o->d_ctr);
+    ASSERT(!o->current_flow) // have no sources
+    
+    // free DataProtoSource
+    DataProtoSource_Free(&o->dp_source);
+    
+    // free BufferWriter
+    BufferWriter_Free(&o->writer);
+}
+
+void DPRelayRouter_SubmitFrame (DPRelayRouter *o, DPRelaySource *src, DPRelaySink *sink, uint8_t *data, int data_len, int num_packets, int inactivity_time)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugObject_Access(&src->d_obj);
+    DebugObject_Access(&sink->d_obj);
+    ASSERT(!o->current_flow)
+    ASSERT(src->router == o)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->frame_mtu)
+    ASSERT(num_packets > 0)
+    
+    // get memory location
+    uint8_t *out;
+    if (!BufferWriter_StartPacket(&o->writer, &out)) {
+        BLog(BLOG_ERROR, "BufferWriter_StartPacket failed for frame %d->%d !?", (int)src->source_id, (int)sink->dest_id);
+        return;
+    }
+    
+    // write frame
+    memcpy(out, data, data_len);
+    
+    // submit frame
+    BufferWriter_EndPacket(&o->writer, data_len);
+    
+    // get a flow
+    // this comes _after_ writing the packet, in case flow initialization schedules jobs
+    struct DPRelay_flow *flow = source_find_flow(src, sink);
+    if (!flow) {
+        if (!(flow = create_flow(src, sink, num_packets, inactivity_time))) {
+            return;
+        }
+    }
+    
+    // remember flow so we know where to route the frame in router_dp_source_handler
+    o->current_flow = flow;
+}
+
+void DPRelaySource_Init (DPRelaySource *o, DPRelayRouter *router, peerid_t source_id, BReactor *reactor)
+{
+    DebugObject_Access(&router->d_obj);
+    
+    // init arguments
+    o->router = router;
+    o->source_id = source_id;
+    
+    // init flows list
+    LinkedList1_Init(&o->flows_list);
+    
+    DebugCounter_Increment(&o->router->d_ctr);
+    DebugObject_Init(&o->d_obj);
+}
+
+void DPRelaySource_Free (DPRelaySource *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Decrement(&o->router->d_ctr);
+    
+    // free flows, detaching them if needed
+    LinkedList1Node *node;
+    while (node = LinkedList1_GetFirst(&o->flows_list)) {
+        struct DPRelay_flow *flow = UPPER_OBJECT(node, struct DPRelay_flow, src_list_node);
+        free_flow(flow);
+    }
+}
+
+void DPRelaySink_Init (DPRelaySink *o, peerid_t dest_id)
+{
+    // init arguments
+    o->dest_id = dest_id;
+    
+    // init flows list
+    LinkedList1_Init(&o->flows_list);
+    
+    // have no sink
+    o->dp_sink = NULL;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void DPRelaySink_Free (DPRelaySink *o)
+{
+    DebugObject_Free(&o->d_obj);
+    ASSERT(!o->dp_sink)
+    
+    // free flows
+    LinkedList1Node *node;
+    while (node = LinkedList1_GetFirst(&o->flows_list)) {
+        struct DPRelay_flow *flow = UPPER_OBJECT(node, struct DPRelay_flow, sink_list_node);
+        free_flow(flow);
+    }
+}
+
+void DPRelaySink_Attach (DPRelaySink *o, DataProtoSink *dp_sink)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->dp_sink)
+    ASSERT(dp_sink)
+    
+    // attach flows
+    for (LinkedList1Node *node = LinkedList1_GetFirst(&o->flows_list); node; node = LinkedList1Node_Next(node)) {
+        struct DPRelay_flow *flow = UPPER_OBJECT(node, struct DPRelay_flow, sink_list_node);
+        DataProtoFlow_Attach(&flow->dp_flow, dp_sink);
+    }
+    
+    // set sink
+    o->dp_sink = dp_sink;
+}
+
+void DPRelaySink_Detach (DPRelaySink *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->dp_sink)
+    
+    // detach flows
+    for (LinkedList1Node *node = LinkedList1_GetFirst(&o->flows_list); node; node = LinkedList1Node_Next(node)) {
+        struct DPRelay_flow *flow = UPPER_OBJECT(node, struct DPRelay_flow, sink_list_node);
+        DataProtoFlow_Detach(&flow->dp_flow);
+    }
+    
+    // set no sink
+    o->dp_sink = NULL;
+}
diff --git a/external/badvpn_dns/client/DPRelay.h b/external/badvpn_dns/client/DPRelay.h
new file mode 100644
index 0000000..c5e16eb
--- /dev/null
+++ b/external/badvpn_dns/client/DPRelay.h
@@ -0,0 +1,89 @@
+/**
+ * @file DPRelay.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_CLIENT_DPRELAY_H
+#define BADVPN_CLIENT_DPRELAY_H
+
+#include <stdint.h>
+#include <limits.h>
+
+#include <protocol/scproto.h>
+#include <protocol/dataproto.h>
+#include <misc/debug.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <flow/BufferWriter.h>
+#include <client/DataProto.h>
+
+struct DPRelay_flow;
+
+typedef struct {
+    int frame_mtu;
+    BufferWriter writer;
+    DataProtoSource dp_source;
+    struct DPRelay_flow *current_flow;
+    DebugObject d_obj;
+    DebugCounter d_ctr;
+} DPRelayRouter;
+
+typedef struct {
+    DPRelayRouter *router;
+    peerid_t source_id;
+    LinkedList1 flows_list;
+    DebugObject d_obj;
+} DPRelaySource;
+
+typedef struct {
+    peerid_t dest_id;
+    LinkedList1 flows_list;
+    DataProtoSink *dp_sink;
+    DebugObject d_obj;
+} DPRelaySink;
+
+struct DPRelay_flow {
+    DPRelaySource *src;
+    DPRelaySink *sink;
+    DataProtoFlow dp_flow;
+    LinkedList1Node src_list_node;
+    LinkedList1Node sink_list_node;
+};
+
+int DPRelayRouter_Init (DPRelayRouter *o, int frame_mtu, BReactor *reactor) WARN_UNUSED;
+void DPRelayRouter_Free (DPRelayRouter *o);
+void DPRelayRouter_SubmitFrame (DPRelayRouter *o, DPRelaySource *src, DPRelaySink *sink, uint8_t *data, int data_len, int num_packets, int inactivity_time);
+
+void DPRelaySource_Init (DPRelaySource *o, DPRelayRouter *router, peerid_t source_id, BReactor *reactor);
+void DPRelaySource_Free (DPRelaySource *o);
+
+void DPRelaySink_Init (DPRelaySink *o, peerid_t dest_id);
+void DPRelaySink_Free (DPRelaySink *o);
+void DPRelaySink_Attach (DPRelaySink *o, DataProtoSink *dp_sink);
+void DPRelaySink_Detach (DPRelaySink *o);
+
+#endif
diff --git a/external/badvpn_dns/client/DataProto.c b/external/badvpn_dns/client/DataProto.c
new file mode 100644
index 0000000..255045a
--- /dev/null
+++ b/external/badvpn_dns/client/DataProto.c
@@ -0,0 +1,566 @@
+/**
+ * @file DataProto.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <protocol/dataproto.h>
+#include <misc/byteorder.h>
+#include <base/BLog.h>
+
+#include <client/DataProto.h>
+
+#include <generated/blog_channel_DataProto.h>
+
+static void monitor_handler (DataProtoSink *o);
+static void refresh_up_job (DataProtoSink *o);
+static void receive_timer_handler (DataProtoSink *o);
+static void notifier_handler (DataProtoSink *o, uint8_t *data, int data_len);
+static void up_job_handler (DataProtoSink *o);
+static void flow_buffer_free (struct DataProtoFlow_buffer *b);
+static void flow_buffer_attach (struct DataProtoFlow_buffer *b, DataProtoSink *sink);
+static void flow_buffer_detach (struct DataProtoFlow_buffer *b);
+static void flow_buffer_schedule_detach (struct DataProtoFlow_buffer *b);
+static void flow_buffer_finish_detach (struct DataProtoFlow_buffer *b);
+static void flow_buffer_qflow_handler_busy (struct DataProtoFlow_buffer *b);
+
+void monitor_handler (DataProtoSink *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // send keep-alive
+    PacketRecvBlocker_AllowBlockedPacket(&o->ka_blocker);
+}
+
+void refresh_up_job (DataProtoSink *o)
+{
+    if (o->up != o->up_report) {
+        BPending_Set(&o->up_job);
+    } else {
+        BPending_Unset(&o->up_job);
+    }
+}
+
+void receive_timer_handler (DataProtoSink *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // consider down
+    o->up = 0;
+    
+    refresh_up_job(o);
+}
+
+void notifier_handler (DataProtoSink *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(data_len >= sizeof(struct dataproto_header))
+    
+    int flags = 0;
+    
+    // if we are receiving keepalives, set the flag
+    if (BTimer_IsRunning(&o->receive_timer)) {
+        flags |= DATAPROTO_FLAGS_RECEIVING_KEEPALIVES;
+    }
+    
+    // modify existing packet here
+    struct dataproto_header header;
+    memcpy(&header, data, sizeof(header));
+    header.flags = hton8(flags);
+    memcpy(data, &header, sizeof(header));
+}
+
+void up_job_handler (DataProtoSink *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->up != o->up_report)
+    
+    o->up_report = o->up;
+    
+    o->handler(o->user, o->up);
+    return;
+}
+
+void source_router_handler (DataProtoSource *o, uint8_t *buf, int recv_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(buf)
+    ASSERT(recv_len >= 0)
+    ASSERT(recv_len <= o->frame_mtu)
+    
+    // remember packet
+    o->current_buf = buf;
+    o->current_recv_len = recv_len;
+    
+    // call handler
+    o->handler(o->user, buf + DATAPROTO_MAX_OVERHEAD, recv_len);
+    return;
+}
+
+void flow_buffer_free (struct DataProtoFlow_buffer *b)
+{
+    ASSERT(!b->sink)
+    
+    // free route buffer
+    RouteBuffer_Free(&b->rbuf);
+    
+    // free inactivity monitor
+    if (b->inactivity_time >= 0) {
+        PacketPassInactivityMonitor_Free(&b->monitor);
+    }
+    
+    // free connector
+    PacketPassConnector_Free(&b->connector);
+    
+    // free buffer structure
+    free(b);
+}
+
+void flow_buffer_attach (struct DataProtoFlow_buffer *b, DataProtoSink *sink)
+{
+    ASSERT(!b->sink)
+    
+    // init queue flow
+    PacketPassFairQueueFlow_Init(&b->sink_qflow, &sink->queue);
+    
+    // connect to queue flow
+    PacketPassConnector_ConnectOutput(&b->connector, PacketPassFairQueueFlow_GetInput(&b->sink_qflow));
+    
+    // set DataProto
+    b->sink = sink;
+}
+
+void flow_buffer_detach (struct DataProtoFlow_buffer *b)
+{
+    ASSERT(b->sink)
+    PacketPassFairQueueFlow_AssertFree(&b->sink_qflow);
+    
+    // disconnect from queue flow
+    PacketPassConnector_DisconnectOutput(&b->connector);
+    
+    // free queue flow
+    PacketPassFairQueueFlow_Free(&b->sink_qflow);
+    
+    // clear reference to this buffer in the sink
+    if (b->sink->detaching_buffer == b) {
+        b->sink->detaching_buffer = NULL;
+    }
+    
+    // set no DataProto
+    b->sink = NULL;
+}
+
+void flow_buffer_schedule_detach (struct DataProtoFlow_buffer *b)
+{
+    ASSERT(b->sink)
+    ASSERT(PacketPassFairQueueFlow_IsBusy(&b->sink_qflow))
+    ASSERT(!b->sink->detaching_buffer || b->sink->detaching_buffer == b)
+    
+    if (b->sink->detaching_buffer == b) {
+        return;
+    }
+    
+    // request cancel
+    PacketPassFairQueueFlow_RequestCancel(&b->sink_qflow);
+    
+    // set busy handler
+    PacketPassFairQueueFlow_SetBusyHandler(&b->sink_qflow, (PacketPassFairQueue_handler_busy)flow_buffer_qflow_handler_busy, b);
+    
+    // remember this buffer in the sink so it can handle us if it goes away
+    b->sink->detaching_buffer = b;
+}
+
+void flow_buffer_finish_detach (struct DataProtoFlow_buffer *b)
+{
+    ASSERT(b->sink)
+    ASSERT(b->sink->detaching_buffer == b)
+    PacketPassFairQueueFlow_AssertFree(&b->sink_qflow);
+    
+    // detach
+    flow_buffer_detach(b);
+    
+    if (!b->flow) {
+        // free
+        flow_buffer_free(b);
+    } else if (b->flow->sink_desired) {
+        // attach
+        flow_buffer_attach(b, b->flow->sink_desired);
+    }
+}
+
+void flow_buffer_qflow_handler_busy (struct DataProtoFlow_buffer *b)
+{
+    ASSERT(b->sink)
+    ASSERT(b->sink->detaching_buffer == b)
+    PacketPassFairQueueFlow_AssertFree(&b->sink_qflow);
+    
+    flow_buffer_finish_detach(b);
+}
+
+int DataProtoSink_Init (DataProtoSink *o, BReactor *reactor, PacketPassInterface *output, btime_t keepalive_time, btime_t tolerance_time, DataProtoSink_handler handler, void *user)
+{
+    ASSERT(PacketPassInterface_HasCancel(output))
+    ASSERT(PacketPassInterface_GetMTU(output) >= DATAPROTO_MAX_OVERHEAD)
+    
+    // init arguments
+    o->reactor = reactor;
+    o->handler = handler;
+    o->user = user;
+    
+    // set frame MTU
+    o->frame_mtu = PacketPassInterface_GetMTU(output) - DATAPROTO_MAX_OVERHEAD;
+    
+    // init notifier
+    PacketPassNotifier_Init(&o->notifier, output, BReactor_PendingGroup(o->reactor));
+    PacketPassNotifier_SetHandler(&o->notifier, (PacketPassNotifier_handler_notify)notifier_handler, o);
+    
+    // init monitor
+    PacketPassInactivityMonitor_Init(&o->monitor, PacketPassNotifier_GetInput(&o->notifier), o->reactor, keepalive_time, (PacketPassInactivityMonitor_handler)monitor_handler, o);
+    PacketPassInactivityMonitor_Force(&o->monitor);
+    
+    // init queue
+    if (!PacketPassFairQueue_Init(&o->queue, PacketPassInactivityMonitor_GetInput(&o->monitor), BReactor_PendingGroup(o->reactor), 1, 1)) {
+        BLog(BLOG_ERROR, "PacketPassFairQueue_Init failed");
+        goto fail1;
+    }
+    
+    // init keepalive queue flow
+    PacketPassFairQueueFlow_Init(&o->ka_qflow, &o->queue);
+    
+    // init keepalive source
+    DataProtoKeepaliveSource_Init(&o->ka_source, BReactor_PendingGroup(o->reactor));
+    
+    // init keepalive blocker
+    PacketRecvBlocker_Init(&o->ka_blocker, DataProtoKeepaliveSource_GetOutput(&o->ka_source), BReactor_PendingGroup(o->reactor));
+    
+    // init keepalive buffer
+    if (!SinglePacketBuffer_Init(&o->ka_buffer, PacketRecvBlocker_GetOutput(&o->ka_blocker), PacketPassFairQueueFlow_GetInput(&o->ka_qflow), BReactor_PendingGroup(o->reactor))) {
+        BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail2;
+    }
+    
+    // init receive timer
+    BTimer_Init(&o->receive_timer, tolerance_time, (BTimer_handler)receive_timer_handler, o);
+    
+    // init handler job
+    BPending_Init(&o->up_job, BReactor_PendingGroup(o->reactor), (BPending_handler)up_job_handler, o);
+    
+    // set not up
+    o->up = 0;
+    o->up_report = 0;
+    
+    // set no detaching buffer
+    o->detaching_buffer = NULL;
+    
+    DebugCounter_Init(&o->d_ctr);
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail2:
+    PacketRecvBlocker_Free(&o->ka_blocker);
+    DataProtoKeepaliveSource_Free(&o->ka_source);
+    PacketPassFairQueueFlow_Free(&o->ka_qflow);
+    PacketPassFairQueue_Free(&o->queue);
+fail1:
+    PacketPassInactivityMonitor_Free(&o->monitor);
+    PacketPassNotifier_Free(&o->notifier);
+    return 0;
+}
+
+void DataProtoSink_Free (DataProtoSink *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Free(&o->d_ctr);
+    
+    // allow freeing queue flows
+    PacketPassFairQueue_PrepareFree(&o->queue);
+    
+    // release detaching buffer
+    if (o->detaching_buffer) {
+        ASSERT(!o->detaching_buffer->flow || o->detaching_buffer->flow->sink_desired != o)
+        flow_buffer_finish_detach(o->detaching_buffer);
+    }
+    
+    // free handler job
+    BPending_Free(&o->up_job);
+    
+    // free receive timer
+    BReactor_RemoveTimer(o->reactor, &o->receive_timer);
+    
+    // free keepalive buffer
+    SinglePacketBuffer_Free(&o->ka_buffer);
+    
+    // free keepalive blocker
+    PacketRecvBlocker_Free(&o->ka_blocker);
+    
+    // free keepalive source
+    DataProtoKeepaliveSource_Free(&o->ka_source);
+    
+    // free keepalive queue flow
+    PacketPassFairQueueFlow_Free(&o->ka_qflow);
+    
+    // free queue
+    PacketPassFairQueue_Free(&o->queue);
+    
+    // free monitor
+    PacketPassInactivityMonitor_Free(&o->monitor);
+    
+    // free notifier
+    PacketPassNotifier_Free(&o->notifier);
+}
+
+void DataProtoSink_Received (DataProtoSink *o, int peer_receiving)
+{
+    ASSERT(peer_receiving == 0 || peer_receiving == 1)
+    DebugObject_Access(&o->d_obj);
+    
+    // reset receive timer
+    BReactor_SetTimer(o->reactor, &o->receive_timer);
+    
+    if (!peer_receiving) {
+        // peer reports not receiving, consider down
+        o->up = 0;
+        // send keep-alive to converge faster
+        PacketRecvBlocker_AllowBlockedPacket(&o->ka_blocker);
+    } else {
+        // consider up
+        o->up = 1;
+    }
+    
+    refresh_up_job(o);
+}
+
+int DataProtoSource_Init (DataProtoSource *o, PacketRecvInterface *input, DataProtoSource_handler handler, void *user, BReactor *reactor)
+{
+    ASSERT(PacketRecvInterface_GetMTU(input) <= INT_MAX - DATAPROTO_MAX_OVERHEAD)
+    ASSERT(handler)
+    
+    // init arguments
+    o->handler = handler;
+    o->user = user;
+    o->reactor = reactor;
+    
+    // remember frame MTU
+    o->frame_mtu = PacketRecvInterface_GetMTU(input);
+    
+    // init router
+    if (!PacketRouter_Init(&o->router, DATAPROTO_MAX_OVERHEAD + o->frame_mtu, DATAPROTO_MAX_OVERHEAD, input, (PacketRouter_handler)source_router_handler, o, BReactor_PendingGroup(reactor))) {
+        BLog(BLOG_ERROR, "PacketRouter_Init failed");
+        goto fail0;
+    }
+    
+    DebugCounter_Init(&o->d_ctr);
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void DataProtoSource_Free (DataProtoSource *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Free(&o->d_ctr);
+    
+    // free router
+    PacketRouter_Free(&o->router);
+}
+
+int DataProtoFlow_Init (DataProtoFlow *o, DataProtoSource *source, peerid_t source_id, peerid_t dest_id, int num_packets, int inactivity_time, void *user,
+                        DataProtoFlow_handler_inactivity handler_inactivity)
+{
+    DebugObject_Access(&source->d_obj);
+    ASSERT(num_packets > 0)
+    ASSERT(!(inactivity_time >= 0) || handler_inactivity)
+    
+    // init arguments
+    o->source = source;
+    o->source_id = source_id;
+    o->dest_id = dest_id;
+    
+    // set no desired sink
+    o->sink_desired = NULL;
+    
+    // allocate buffer structure
+    struct DataProtoFlow_buffer *b = (struct DataProtoFlow_buffer *)malloc(sizeof(*b));
+    if (!b) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    o->b = b;
+    
+    // set parent
+    b->flow = o;
+    
+    // remember inactivity time
+    b->inactivity_time = inactivity_time;
+    
+    // init connector
+    PacketPassConnector_Init(&b->connector, DATAPROTO_MAX_OVERHEAD + source->frame_mtu, BReactor_PendingGroup(source->reactor));
+    
+    // init inactivity monitor
+    PacketPassInterface *buf_out = PacketPassConnector_GetInput(&b->connector);
+    if (b->inactivity_time >= 0) {
+        PacketPassInactivityMonitor_Init(&b->monitor, buf_out, source->reactor, b->inactivity_time, handler_inactivity, user);
+        buf_out = PacketPassInactivityMonitor_GetInput(&b->monitor);
+    }
+    
+    // init route buffer
+    if (!RouteBuffer_Init(&b->rbuf, DATAPROTO_MAX_OVERHEAD + source->frame_mtu, buf_out, num_packets)) {
+        BLog(BLOG_ERROR, "RouteBuffer_Init failed");
+        goto fail1;
+    }
+    
+    // set no sink
+    b->sink = NULL;
+    
+    DebugCounter_Increment(&source->d_ctr);
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    if (b->inactivity_time >= 0) {
+        PacketPassInactivityMonitor_Free(&b->monitor);
+    }
+    PacketPassConnector_Free(&b->connector);
+    free(b);
+fail0:
+    return 0;
+}
+
+void DataProtoFlow_Free (DataProtoFlow *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugCounter_Decrement(&o->source->d_ctr);
+    ASSERT(!o->sink_desired)
+    struct DataProtoFlow_buffer *b = o->b;
+    
+    if (b->sink) {
+        if (PacketPassFairQueueFlow_IsBusy(&b->sink_qflow)) {
+            // schedule detach, free buffer after detach
+            flow_buffer_schedule_detach(b);
+            b->flow = NULL;
+            
+            // remove inactivity handler
+            if (b->inactivity_time >= 0) {
+                PacketPassInactivityMonitor_SetHandler(&b->monitor, NULL, NULL);
+            }
+        } else {
+            // detach and free buffer now
+            flow_buffer_detach(b);
+            flow_buffer_free(b);
+        }
+    } else {
+        // free buffer
+        flow_buffer_free(b);
+    }
+}
+
+void DataProtoFlow_Route (DataProtoFlow *o, int more)
+{
+    DebugObject_Access(&o->d_obj);
+    PacketRouter_AssertRoute(&o->source->router);
+    ASSERT(o->source->current_buf)
+    ASSERT(more == 0 || more == 1)
+    struct DataProtoFlow_buffer *b = o->b;
+    
+    // write header. Don't set flags, it will be set in notifier_handler.
+    struct dataproto_header header;
+    struct dataproto_peer_id id;
+    header.from_id = htol16(o->source_id);
+    header.num_peer_ids = htol16(1);
+    id.id = htol16(o->dest_id);
+    memcpy(o->source->current_buf, &header, sizeof(header));
+    memcpy(o->source->current_buf + sizeof(header), &id, sizeof(id));
+    
+    // route
+    uint8_t *next_buf;
+    if (!PacketRouter_Route(&o->source->router, DATAPROTO_MAX_OVERHEAD + o->source->current_recv_len, &b->rbuf,
+                            &next_buf, DATAPROTO_MAX_OVERHEAD, (more ? o->source->current_recv_len : 0)
+    )) {
+        BLog(BLOG_NOTICE, "buffer full: %d->%d", (int)o->source_id, (int)o->dest_id);
+        return;
+    }
+    
+    // remember next buffer, or don't allow further routing if more==0
+    o->source->current_buf = (more ? next_buf : NULL);
+}
+
+void DataProtoFlow_Attach (DataProtoFlow *o, DataProtoSink *sink)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugObject_Access(&sink->d_obj);
+    ASSERT(!o->sink_desired)
+    ASSERT(sink)
+    ASSERT(o->source->frame_mtu <= sink->frame_mtu)
+    struct DataProtoFlow_buffer *b = o->b;
+    
+    if (b->sink) {
+        if (PacketPassFairQueueFlow_IsBusy(&b->sink_qflow)) {
+            // schedule detach and reattach
+            flow_buffer_schedule_detach(b);
+        } else {
+            // detach and reattach now
+            flow_buffer_detach(b);
+            flow_buffer_attach(b, sink);
+        }
+    } else {
+        // attach
+        flow_buffer_attach(b, sink);
+    }
+    
+    // set desired sink
+    o->sink_desired = sink;
+    
+    DebugCounter_Increment(&sink->d_ctr);
+}
+
+void DataProtoFlow_Detach (DataProtoFlow *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->sink_desired)
+    struct DataProtoFlow_buffer *b = o->b;
+    ASSERT(b->sink)
+    
+    DataProtoSink *sink = o->sink_desired;
+    
+    if (PacketPassFairQueueFlow_IsBusy(&b->sink_qflow)) {
+        // schedule detach
+        flow_buffer_schedule_detach(b);
+    } else {
+        // detach now
+        flow_buffer_detach(b);
+    }
+    
+    // set no desired sink
+    o->sink_desired = NULL;
+    
+    DebugCounter_Decrement(&sink->d_ctr);
+}
diff --git a/external/badvpn_dns/client/DataProto.h b/external/badvpn_dns/client/DataProto.h
new file mode 100644
index 0000000..0da3a20
--- /dev/null
+++ b/external/badvpn_dns/client/DataProto.h
@@ -0,0 +1,237 @@
+/**
+ * @file DataProto.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Mudule for frame sending used in the VPN client program.
+ */
+
+#ifndef BADVPN_CLIENT_DATAPROTO_H
+#define BADVPN_CLIENT_DATAPROTO_H
+
+#include <stdint.h>
+
+#include <misc/debugcounter.h>
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <flow/PacketPassFairQueue.h>
+#include <flow/PacketPassNotifier.h>
+#include <flow/PacketRecvBlocker.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketPassConnector.h>
+#include <flow/PacketRouter.h>
+#include <flowextra/PacketPassInactivityMonitor.h>
+#include <client/DataProtoKeepaliveSource.h>
+
+typedef void (*DataProtoSink_handler) (void *user, int up);
+typedef void (*DataProtoSource_handler) (void *user, const uint8_t *frame, int frame_len);
+typedef void (*DataProtoFlow_handler_inactivity) (void *user);
+
+struct DataProtoFlow_buffer;
+
+/**
+ * Frame destination.
+ * Represents a peer as a destination for sending frames to.
+ */
+typedef struct {
+    BReactor *reactor;
+    int frame_mtu;
+    PacketPassFairQueue queue;
+    PacketPassInactivityMonitor monitor;
+    PacketPassNotifier notifier;
+    DataProtoKeepaliveSource ka_source;
+    PacketRecvBlocker ka_blocker;
+    SinglePacketBuffer ka_buffer;
+    PacketPassFairQueueFlow ka_qflow;
+    BTimer receive_timer;
+    int up;
+    int up_report;
+    DataProtoSink_handler handler;
+    void *user;
+    BPending up_job;
+    struct DataProtoFlow_buffer *detaching_buffer;
+    DebugObject d_obj;
+    DebugCounter d_ctr;
+} DataProtoSink;
+
+/**
+ * Receives frames from a {@link PacketRecvInterface} input and
+ * allows the user to route them to buffers in {@link DataProtoFlow}'s.
+ */
+typedef struct {
+    DataProtoSource_handler handler;
+    void *user;
+    BReactor *reactor;
+    int frame_mtu;
+    PacketRouter router;
+    uint8_t *current_buf;
+    int current_recv_len;
+    DebugObject d_obj;
+    DebugCounter d_ctr;
+} DataProtoSource;
+
+/**
+ * Contains a buffer for frames from a specific peer to a specific peer.
+ * Receives frames from a {@link DataProtoSource} as routed by the user.
+ * Can be attached to a {@link DataProtoSink} to send out frames.
+ */
+typedef struct {
+    DataProtoSource *source;
+    peerid_t source_id;
+    peerid_t dest_id;
+    DataProtoSink *sink_desired;
+    struct DataProtoFlow_buffer *b;
+    DebugObject d_obj;
+} DataProtoFlow;
+
+struct DataProtoFlow_buffer {
+    DataProtoFlow *flow;
+    int inactivity_time;
+    RouteBuffer rbuf;
+    PacketPassInactivityMonitor monitor;
+    PacketPassConnector connector;
+    DataProtoSink *sink;
+    PacketPassFairQueueFlow sink_qflow;
+};
+
+/**
+ * Initializes the sink.
+ * 
+ * @param o the object
+ * @param reactor reactor we live in
+ * @param output output interface. Must support cancel functionality. Its MTU must be
+ *               >=DATAPROTO_MAX_OVERHEAD.
+ * @param keepalive_time keepalive time
+ * @param tolerance_time after how long of not having received anything from the peer
+ *                       to consider the link down
+ * @param handler up state handler
+ * @param user value to pass to handler
+ * @return 1 on success, 0 on failure
+ */
+int DataProtoSink_Init (DataProtoSink *o, BReactor *reactor, PacketPassInterface *output, btime_t keepalive_time, btime_t tolerance_time, DataProtoSink_handler handler, void *user) WARN_UNUSED;
+
+/**
+ * Frees the sink.
+ * There must be no local sources attached.
+ * 
+ * @param o the object
+ */
+void DataProtoSink_Free (DataProtoSink *o);
+
+/**
+ * Notifies the sink that a packet was received from the peer.
+ * Must not be in freeing state.
+ * 
+ * @param o the object
+ * @param peer_receiving whether the DATAPROTO_FLAGS_RECEIVING_KEEPALIVES flag was set in the packet.
+ *                       Must be 0 or 1.
+ */
+void DataProtoSink_Received (DataProtoSink *o, int peer_receiving);
+
+/**
+ * Initiazes the source.
+ * 
+ * @param o the object
+ * @param input frame input. Its input MTU must be <= INT_MAX - DATAPROTO_MAX_OVERHEAD.
+ * @param handler handler called when a frame arrives to allow the user to route it to
+ *                appropriate {@link DataProtoFlow}'s.
+ * @param user value passed to handler
+ * @param reactor reactor we live in
+ * @return 1 on success, 0 on failure
+ */
+int DataProtoSource_Init (DataProtoSource *o, PacketRecvInterface *input, DataProtoSource_handler handler, void *user, BReactor *reactor) WARN_UNUSED;
+
+/**
+ * Frees the source.
+ * There must be no {@link DataProtoFlow}'s using this source.
+ * 
+ * @param o the object
+ */
+void DataProtoSource_Free (DataProtoSource *o);
+
+/**
+ * Initializes the flow.
+ * The flow is initialized in not attached state.
+ * 
+ * @param o the object
+ * @param source source to receive frames from
+ * @param source_id source peer ID to encode in the headers (i.e. our ID)
+ * @param dest_id destination peer ID to encode in the headers (i.e. ID if the peer this
+ *                flow belongs to)
+ * @param num_packets number of packets the buffer should hold. Must be >0.
+ * @param inactivity_time milliseconds of output inactivity after which to call the
+ *                        inactivity handler; <0 to disable. Note that the flow is considered
+ *                        active as long as its buffer is non-empty, even if is not attached to
+ *                        a {@link DataProtoSink}.
+ * @param user value to pass to handler
+ * @param handler_inactivity inactivity handler, if inactivity_time >=0
+ * @return 1 on success, 0 on failure
+ */
+int DataProtoFlow_Init (DataProtoFlow *o, DataProtoSource *source, peerid_t source_id, peerid_t dest_id, int num_packets, int inactivity_time, void *user,
+                        DataProtoFlow_handler_inactivity handler_inactivity) WARN_UNUSED;
+
+/**
+ * Frees the flow.
+ * The flow must be in not attached state.
+ * 
+ * @param o the object
+ */
+void DataProtoFlow_Free (DataProtoFlow *o);
+
+/**
+ * Routes a frame from the flow's source to this flow.
+ * Must be called from within the job context of the {@link DataProtoSource_handler} handler.
+ * Must not be called after this has been called with more=0 for the current frame.
+ * 
+ * @param o the object
+ * @param more whether the current frame may have to be routed to more
+ *             flows. If 0, must not be called again until the handler is
+ *             called for the next frame. Must be 0 or 1.
+ */
+void DataProtoFlow_Route (DataProtoFlow *o, int more);
+
+/**
+ * Attaches the flow to a sink.
+ * The flow must be in not attached state.
+ * 
+ * @param o the object
+ * @param sink sink to attach to. This flow's frame_mtu must be <=
+ *             (output MTU of sink) - DATAPROTO_MAX_OVERHEAD.
+ */
+void DataProtoFlow_Attach (DataProtoFlow *o, DataProtoSink *sink);
+
+/**
+ * Detaches the flow from a destination.
+ * The flow must be in attached state.
+ * 
+ * @param o the object
+ */
+void DataProtoFlow_Detach (DataProtoFlow *o);
+
+#endif
diff --git a/external/badvpn_dns/client/DataProtoKeepaliveSource.c b/external/badvpn_dns/client/DataProtoKeepaliveSource.c
new file mode 100644
index 0000000..834c42f
--- /dev/null
+++ b/external/badvpn_dns/client/DataProtoKeepaliveSource.c
@@ -0,0 +1,72 @@
+/**
+ * @file DataProtoKeepaliveSource.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include <protocol/dataproto.h>
+#include <misc/byteorder.h>
+
+#include "DataProtoKeepaliveSource.h"
+
+static void output_handler_recv (DataProtoKeepaliveSource *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    struct dataproto_header header;
+    header.flags = htol8(0);
+    header.from_id = htol16(0);
+    header.num_peer_ids = htol16(0);
+    memcpy(data, &header, sizeof(header));
+    
+    // finish packet
+    PacketRecvInterface_Done(&o->output, sizeof(struct dataproto_header));
+}
+
+void DataProtoKeepaliveSource_Init (DataProtoKeepaliveSource *o, BPendingGroup *pg)
+{
+    // init output
+    PacketRecvInterface_Init(&o->output, sizeof(struct dataproto_header), (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void DataProtoKeepaliveSource_Free (DataProtoKeepaliveSource *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free output
+    PacketRecvInterface_Free(&o->output);
+}
+
+PacketRecvInterface * DataProtoKeepaliveSource_GetOutput (DataProtoKeepaliveSource *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
diff --git a/external/badvpn_dns/client/DataProtoKeepaliveSource.h b/external/badvpn_dns/client/DataProtoKeepaliveSource.h
new file mode 100644
index 0000000..4488e24
--- /dev/null
+++ b/external/badvpn_dns/client/DataProtoKeepaliveSource.h
@@ -0,0 +1,73 @@
+/**
+ * @file DataProtoKeepaliveSource.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * A {@link PacketRecvInterface} source which provides DataProto keepalive packets.
+ */
+
+#ifndef BADVPN_DATAPROTOKEEPALIVESOURCE_H
+#define BADVPN_DATAPROTOKEEPALIVESOURCE_H
+
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * A {@link PacketRecvInterface} source which provides DataProto keepalive packets.
+ * These packets have no payload, no destination peers and flags zero.
+ */
+typedef struct {
+    DebugObject d_obj;
+    PacketRecvInterface output;
+} DataProtoKeepaliveSource;
+
+/**
+ * Initializes the object.
+ *
+ * @param o the object
+ * @param pg pending group
+ */
+void DataProtoKeepaliveSource_Init (DataProtoKeepaliveSource *o, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void DataProtoKeepaliveSource_Free (DataProtoKeepaliveSource *o);
+
+/**
+ * Returns the output interface.
+ * The MTU of the output interface will be sizeof(struct dataproto_header).
+ *
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * DataProtoKeepaliveSource_GetOutput (DataProtoKeepaliveSource *o);
+
+#endif
diff --git a/external/badvpn_dns/client/DatagramPeerIO.c b/external/badvpn_dns/client/DatagramPeerIO.c
new file mode 100644
index 0000000..e3a8f68
--- /dev/null
+++ b/external/badvpn_dns/client/DatagramPeerIO.c
@@ -0,0 +1,425 @@
+/**
+ * @file DatagramPeerIO.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <client/DatagramPeerIO.h>
+
+#include <generated/blog_channel_DatagramPeerIO.h>
+
+#define DATAGRAMPEERIO_MODE_NONE 0
+#define DATAGRAMPEERIO_MODE_CONNECT 1
+#define DATAGRAMPEERIO_MODE_BIND 2
+
+#define PeerLog(_o, ...) BLog_LogViaFunc((_o)->logfunc, (_o)->user, BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void init_io (DatagramPeerIO *o);
+static void free_io (DatagramPeerIO *o);
+static void dgram_handler (DatagramPeerIO *o, int event);
+static void reset_mode (DatagramPeerIO *o);
+static void recv_decoder_notifier_handler (DatagramPeerIO *o, uint8_t *data, int data_len);
+
+void init_io (DatagramPeerIO *o)
+{
+    // init dgram recv interface
+    BDatagram_RecvAsync_Init(&o->dgram, o->effective_socket_mtu);
+    
+    // connect source
+    PacketRecvConnector_ConnectInput(&o->recv_connector, BDatagram_RecvAsync_GetIf(&o->dgram));
+    
+    // init dgram send interface
+    BDatagram_SendAsync_Init(&o->dgram, o->effective_socket_mtu);
+    
+    // connect sink
+    PacketPassConnector_ConnectOutput(&o->send_connector, BDatagram_SendAsync_GetIf(&o->dgram));
+}
+
+void free_io (DatagramPeerIO *o)
+{
+    // disconnect sink
+    PacketPassConnector_DisconnectOutput(&o->send_connector);
+    
+    // free dgram send interface
+    BDatagram_SendAsync_Free(&o->dgram);
+    
+    // disconnect source
+    PacketRecvConnector_DisconnectInput(&o->recv_connector);
+    
+    // free dgram recv interface
+    BDatagram_RecvAsync_Free(&o->dgram);
+}
+
+void dgram_handler (DatagramPeerIO *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->mode == DATAGRAMPEERIO_MODE_CONNECT || o->mode == DATAGRAMPEERIO_MODE_BIND)
+    
+    PeerLog(o, BLOG_NOTICE, "error");
+    
+    // reset mode
+    reset_mode(o);
+    
+    // report error
+    if (o->handler_error) {
+        o->handler_error(o->user);
+        return;
+    }
+}
+
+void reset_mode (DatagramPeerIO *o)
+{
+    ASSERT(o->mode == DATAGRAMPEERIO_MODE_NONE || o->mode == DATAGRAMPEERIO_MODE_CONNECT || o->mode == DATAGRAMPEERIO_MODE_BIND)
+    
+    if (o->mode == DATAGRAMPEERIO_MODE_NONE) {
+        return;
+    }
+    
+    // remove recv notifier handler
+    PacketPassNotifier_SetHandler(&o->recv_notifier, NULL, NULL);
+    
+    // free I/O
+    free_io(o);
+    
+    // free datagram object
+    BDatagram_Free(&o->dgram);
+    
+    // set mode
+    o->mode = DATAGRAMPEERIO_MODE_NONE;
+}
+
+void recv_decoder_notifier_handler (DatagramPeerIO *o, uint8_t *data, int data_len)
+{
+    ASSERT(o->mode == DATAGRAMPEERIO_MODE_BIND)
+    DebugObject_Access(&o->d_obj);
+    
+    // obtain addresses from last received packet
+    BAddr addr;
+    BIPAddr local_addr;
+    ASSERT_EXECUTE(BDatagram_GetLastReceiveAddrs(&o->dgram, &addr, &local_addr))
+    
+    // check address family just in case
+    if (!BDatagram_AddressFamilySupported(addr.type)) {
+        PeerLog(o, BLOG_ERROR, "unsupported receive address");
+        return;
+    }
+    
+    // update addresses
+    BDatagram_SetSendAddrs(&o->dgram, addr, local_addr);
+}
+
+int DatagramPeerIO_Init (
+    DatagramPeerIO *o,
+    BReactor *reactor,
+    int payload_mtu,
+    int socket_mtu,
+    struct spproto_security_params sp_params,
+    btime_t latency,
+    int num_frames,
+    PacketPassInterface *recv_userif,
+    int otp_warning_count,
+    BThreadWorkDispatcher *twd,
+    void *user,
+    BLog_logfunc logfunc,
+    DatagramPeerIO_handler_error handler_error,
+    DatagramPeerIO_handler_otp_warning handler_otp_warning,
+    DatagramPeerIO_handler_otp_ready handler_otp_ready
+)
+{
+    ASSERT(payload_mtu >= 0)
+    ASSERT(socket_mtu >= 0)
+    spproto_assert_security_params(sp_params);
+    ASSERT(num_frames > 0)
+    ASSERT(PacketPassInterface_GetMTU(recv_userif) >= payload_mtu)
+    if (SPPROTO_HAVE_OTP(sp_params)) {
+        ASSERT(otp_warning_count > 0)
+        ASSERT(otp_warning_count <= sp_params.otp_num)
+    }
+    
+    // set parameters
+    o->reactor = reactor;
+    o->payload_mtu = payload_mtu;
+    o->sp_params = sp_params;
+    o->user = user;
+    o->logfunc = logfunc;
+    o->handler_error = handler_error;
+    
+    // check num frames (for FragmentProtoAssembler)
+    if (num_frames >= FPA_MAX_TIME) {
+        PeerLog(o, BLOG_ERROR, "num_frames is too big");
+        goto fail0;
+    }
+    
+    // check payload MTU (for FragmentProto)
+    if (o->payload_mtu > UINT16_MAX) {
+        PeerLog(o, BLOG_ERROR, "payload MTU is too big");
+        goto fail0;
+    }
+    
+    // calculate SPProto payload MTU
+    if ((o->spproto_payload_mtu = spproto_payload_mtu_for_carrier_mtu(o->sp_params, socket_mtu)) <= (int)sizeof(struct fragmentproto_chunk_header)) {
+        PeerLog(o, BLOG_ERROR, "socket MTU is too small");
+        goto fail0;
+    }
+    
+    // calculate effective socket MTU
+    if ((o->effective_socket_mtu = spproto_carrier_mtu_for_payload_mtu(o->sp_params, o->spproto_payload_mtu)) < 0) {
+        PeerLog(o, BLOG_ERROR, "spproto_carrier_mtu_for_payload_mtu failed !?");
+        goto fail0;
+    }
+    
+    // init receiving
+    
+    // init assembler
+    if (!FragmentProtoAssembler_Init(&o->recv_assembler, o->spproto_payload_mtu, recv_userif, num_frames, fragmentproto_max_chunks_for_frame(o->spproto_payload_mtu, o->payload_mtu),
+                                     BReactor_PendingGroup(o->reactor), o->user, o->logfunc
+    )) {
+        PeerLog(o, BLOG_ERROR, "FragmentProtoAssembler_Init failed");
+        goto fail0;
+    }
+    
+    // init notifier
+    PacketPassNotifier_Init(&o->recv_notifier, FragmentProtoAssembler_GetInput(&o->recv_assembler), BReactor_PendingGroup(o->reactor));
+    
+    // init decoder
+    if (!SPProtoDecoder_Init(&o->recv_decoder, PacketPassNotifier_GetInput(&o->recv_notifier), o->sp_params, 2, BReactor_PendingGroup(o->reactor), twd, o->user, o->logfunc)) {
+        PeerLog(o, BLOG_ERROR, "SPProtoDecoder_Init failed");
+        goto fail1;
+    }
+    SPProtoDecoder_SetHandlers(&o->recv_decoder, handler_otp_ready, user);
+    
+    // init connector
+    PacketRecvConnector_Init(&o->recv_connector, o->effective_socket_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // init buffer
+    if (!SinglePacketBuffer_Init(&o->recv_buffer, PacketRecvConnector_GetOutput(&o->recv_connector), SPProtoDecoder_GetInput(&o->recv_decoder), BReactor_PendingGroup(o->reactor))) {
+        PeerLog(o, BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail2;
+    }
+    
+    // init sending base
+    
+    // init disassembler
+    FragmentProtoDisassembler_Init(&o->send_disassembler, o->reactor, o->payload_mtu, o->spproto_payload_mtu, -1, latency);
+    
+    // init encoder
+    if (!SPProtoEncoder_Init(&o->send_encoder, FragmentProtoDisassembler_GetOutput(&o->send_disassembler), o->sp_params, otp_warning_count, BReactor_PendingGroup(o->reactor), twd)) {
+        PeerLog(o, BLOG_ERROR, "SPProtoEncoder_Init failed");
+        goto fail3;
+    }
+    SPProtoEncoder_SetHandlers(&o->send_encoder, handler_otp_warning, user);
+    
+    // init connector
+    PacketPassConnector_Init(&o->send_connector, o->effective_socket_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // init buffer
+    if (!SinglePacketBuffer_Init(&o->send_buffer, SPProtoEncoder_GetOutput(&o->send_encoder), PacketPassConnector_GetInput(&o->send_connector), BReactor_PendingGroup(o->reactor))) {
+        PeerLog(o, BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail4;
+    }
+    
+    // set mode
+    o->mode = DATAGRAMPEERIO_MODE_NONE;
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail4:
+    PacketPassConnector_Free(&o->send_connector);
+    SPProtoEncoder_Free(&o->send_encoder);
+fail3:
+    FragmentProtoDisassembler_Free(&o->send_disassembler);
+    SinglePacketBuffer_Free(&o->recv_buffer);
+fail2:
+    PacketRecvConnector_Free(&o->recv_connector);
+    SPProtoDecoder_Free(&o->recv_decoder);
+fail1:
+    PacketPassNotifier_Free(&o->recv_notifier);
+    FragmentProtoAssembler_Free(&o->recv_assembler);
+fail0:
+    return 0;
+}
+
+void DatagramPeerIO_Free (DatagramPeerIO *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // reset mode
+    reset_mode(o);
+    
+    // free sending base
+    SinglePacketBuffer_Free(&o->send_buffer);
+    PacketPassConnector_Free(&o->send_connector);
+    SPProtoEncoder_Free(&o->send_encoder);
+    FragmentProtoDisassembler_Free(&o->send_disassembler);
+    
+    // free receiving
+    SinglePacketBuffer_Free(&o->recv_buffer);
+    PacketRecvConnector_Free(&o->recv_connector);
+    SPProtoDecoder_Free(&o->recv_decoder);
+    PacketPassNotifier_Free(&o->recv_notifier);
+    FragmentProtoAssembler_Free(&o->recv_assembler);
+}
+
+PacketPassInterface * DatagramPeerIO_GetSendInput (DatagramPeerIO *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return FragmentProtoDisassembler_GetInput(&o->send_disassembler);
+}
+
+int DatagramPeerIO_Connect (DatagramPeerIO *o, BAddr addr)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // reset mode
+    reset_mode(o);
+    
+    // check address
+    if (!BDatagram_AddressFamilySupported(addr.type)) {
+        PeerLog(o, BLOG_ERROR, "BDatagram_AddressFamilySupported failed");
+        goto fail0;
+    }
+    
+    // init dgram
+    if (!BDatagram_Init(&o->dgram, addr.type, o->reactor, o, (BDatagram_handler)dgram_handler)) {
+        PeerLog(o, BLOG_ERROR, "BDatagram_Init failed");
+        goto fail0;
+    }
+    
+    // set send address
+    BIPAddr local_addr;
+    BIPAddr_InitInvalid(&local_addr);
+    BDatagram_SetSendAddrs(&o->dgram, addr, local_addr);
+    
+    // init I/O
+    init_io(o);
+    
+    // set mode
+    o->mode = DATAGRAMPEERIO_MODE_CONNECT;
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+int DatagramPeerIO_Bind (DatagramPeerIO *o, BAddr addr)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(BDatagram_AddressFamilySupported(addr.type))
+    
+    // reset mode
+    reset_mode(o);
+    
+    // init dgram
+    if (!BDatagram_Init(&o->dgram, addr.type, o->reactor, o, (BDatagram_handler)dgram_handler)) {
+        PeerLog(o, BLOG_ERROR, "BDatagram_Init failed");
+        goto fail0;
+    }
+    
+    // bind dgram
+    if (!BDatagram_Bind(&o->dgram, addr)) {
+        PeerLog(o, BLOG_INFO, "BDatagram_Bind failed");
+        goto fail1;
+    }
+    
+    // init I/O
+    init_io(o);
+    
+    // set recv notifier handler
+    PacketPassNotifier_SetHandler(&o->recv_notifier, (PacketPassNotifier_handler_notify)recv_decoder_notifier_handler, o);
+    
+    // set mode
+    o->mode = DATAGRAMPEERIO_MODE_BIND;
+    
+    return 1;
+    
+fail1:
+    BDatagram_Free(&o->dgram);
+fail0:
+    return 0;
+}
+
+void DatagramPeerIO_SetEncryptionKey (DatagramPeerIO *o, uint8_t *encryption_key)
+{
+    ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // set sending key
+    SPProtoEncoder_SetEncryptionKey(&o->send_encoder, encryption_key);
+    
+    // set receiving key
+    SPProtoDecoder_SetEncryptionKey(&o->recv_decoder, encryption_key);
+}
+
+void DatagramPeerIO_RemoveEncryptionKey (DatagramPeerIO *o)
+{
+    ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // remove sending key
+    SPProtoEncoder_RemoveEncryptionKey(&o->send_encoder);
+    
+    // remove receiving key
+    SPProtoDecoder_RemoveEncryptionKey(&o->recv_decoder);
+}
+
+void DatagramPeerIO_SetOTPSendSeed (DatagramPeerIO *o, uint16_t seed_id, uint8_t *key, uint8_t *iv)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // set sending seed
+    SPProtoEncoder_SetOTPSeed(&o->send_encoder, seed_id, key, iv);
+}
+
+void DatagramPeerIO_RemoveOTPSendSeed (DatagramPeerIO *o)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // remove sending seed
+    SPProtoEncoder_RemoveOTPSeed(&o->send_encoder);
+}
+
+void DatagramPeerIO_AddOTPRecvSeed (DatagramPeerIO *o, uint16_t seed_id, uint8_t *key, uint8_t *iv)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // add receiving seed
+    SPProtoDecoder_AddOTPSeed(&o->recv_decoder, seed_id, key, iv);
+}
+
+void DatagramPeerIO_RemoveOTPRecvSeeds (DatagramPeerIO *o)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // remove receiving seeds
+    SPProtoDecoder_RemoveOTPSeeds(&o->recv_decoder);
+}
diff --git a/external/badvpn_dns/client/DatagramPeerIO.h b/external/badvpn_dns/client/DatagramPeerIO.h
new file mode 100644
index 0000000..5d19b5a
--- /dev/null
+++ b/external/badvpn_dns/client/DatagramPeerIO.h
@@ -0,0 +1,271 @@
+/**
+ * @file DatagramPeerIO.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object for comminicating with a peer using a datagram socket.
+ */
+
+#ifndef BADVPN_CLIENT_DATAGRAMPEERIO_H
+#define BADVPN_CLIENT_DATAGRAMPEERIO_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <protocol/spproto.h>
+#include <protocol/fragmentproto.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BAddr.h>
+#include <system/BDatagram.h>
+#include <system/BTime.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketPassConnector.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketRecvConnector.h>
+#include <flow/PacketPassNotifier.h>
+#include <client/FragmentProtoDisassembler.h>
+#include <client/FragmentProtoAssembler.h>
+#include <client/SPProtoEncoder.h>
+#include <client/SPProtoDecoder.h>
+
+/**
+ * Callback function invoked when an error occurs with the peer connection.
+ * The object has entered default state.
+ * May be called from within a sending Send call.
+ *
+ * @param user as in {@link DatagramPeerIO_SetHandlers}
+ */
+typedef void (*DatagramPeerIO_handler_error) (void *user);
+
+/**
+ * Handler function invoked when the number of used OTPs has reached
+ * the specified warning number in {@link DatagramPeerIO_SetOTPWarningHandler}.
+ * May be called from within a sending Send call.
+ *
+ * @param user as in {@link DatagramPeerIO_SetHandlers}
+ */
+typedef void (*DatagramPeerIO_handler_otp_warning) (void *user);
+
+/**
+ * Handler called when OTP generation for a new receive seed is finished.
+ * 
+ * @param user as in {@link DatagramPeerIO_SetHandlers}
+ */
+typedef void (*DatagramPeerIO_handler_otp_ready) (void *user);
+
+/**
+ * Object for comminicating with a peer using a datagram socket.
+ *
+ * The user provides data for sending to the peer through {@link PacketPassInterface}.
+ * Received data is provided to the user through {@link PacketPassInterface}.
+ *
+ * The object has a logical state called a mode, which is one of the following:
+ *     - default - nothing is send or received
+ *     - connecting - an address was provided by the user for sending datagrams to.
+ *                    Datagrams are being sent to that address through a socket,
+ *                    and datagrams are being received on the same socket.
+ *     - binding - an address was provided by the user to bind a socket to.
+ *                 Datagrams are being received on the socket. Datagrams are not being
+ *                 sent initially. When a datagram is received, its source address is
+ *                 used as a destination address for sending datagrams.
+ */
+typedef struct {
+    DebugObject d_obj;
+    BReactor *reactor;
+    int payload_mtu;
+    struct spproto_security_params sp_params;
+    void *user;
+    BLog_logfunc logfunc;
+    DatagramPeerIO_handler_error handler_error;
+    int spproto_payload_mtu;
+    int effective_socket_mtu;
+    
+    // sending base
+    FragmentProtoDisassembler send_disassembler;
+    SPProtoEncoder send_encoder;
+    SinglePacketBuffer send_buffer;
+    PacketPassConnector send_connector;
+    
+    // receiving
+    PacketRecvConnector recv_connector;
+    SinglePacketBuffer recv_buffer;
+    SPProtoDecoder recv_decoder;
+    PacketPassNotifier recv_notifier;
+    FragmentProtoAssembler recv_assembler;
+    
+    // mode
+    int mode;
+    
+    // datagram object
+    BDatagram dgram;
+} DatagramPeerIO;
+
+/**
+ * Initializes the object.
+ * The interface is initialized in default mode.
+ * {@link BLog_Init} must have been done.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if
+ * {@link BThreadWorkDispatcher_UsingThreads}(twd) = 1.
+ *
+ * @param o the object
+ * @param reactor {@link BReactor} we live in
+ * @param payload_mtu maximum payload size. Must be >=0.
+ * @param socket_mtu maximum datagram size for the socket. Must be >=0. Must be large enough so it is possible to
+ *                   send a FragmentProto chunk with one byte of data over SPProto, i.e. the following has to hold:
+ *                   spproto_payload_mtu_for_carrier_mtu(sp_params, socket_mtu) > sizeof(struct fragmentproto_chunk_header)
+ * @param sp_params SPProto security parameters
+ * @param latency latency parameter to {@link FragmentProtoDisassembler_Init}.
+ * @param num_frames num_frames parameter to {@link FragmentProtoAssembler_Init}. Must be >0.
+ * @param recv_userif interface to pass received packets to the user. Its MTU must be >=payload_mtu.
+ * @param otp_warning_count If using OTPs, after how many encoded packets to call the handler.
+ *                          In this case, must be >0 and <=sp_params.otp_num.
+ * @param twd thread work dispatcher
+ * @param user value to pass to handlers
+ * @param logfunc function which prepends the log prefix using {@link BLog_Append}
+ * @param handler_error error handler
+ * @param handler_otp_warning OTP warning handler
+ * @param handler_otp_ready handler called when OTP generation for a new receive seed is finished
+ * @return 1 on success, 0 on failure
+ */
+int DatagramPeerIO_Init (
+    DatagramPeerIO *o,
+    BReactor *reactor,
+    int payload_mtu,
+    int socket_mtu,
+    struct spproto_security_params sp_params,
+    btime_t latency,
+    int num_frames,
+    PacketPassInterface *recv_userif,
+    int otp_warning_count,
+    BThreadWorkDispatcher *twd,
+    void *user,
+    BLog_logfunc logfunc,
+    DatagramPeerIO_handler_error handler_error,
+    DatagramPeerIO_handler_otp_warning handler_otp_warning,
+    DatagramPeerIO_handler_otp_ready handler_otp_ready
+) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void DatagramPeerIO_Free (DatagramPeerIO *o);
+
+/**
+ * Returns an interface the user should use to send packets.
+ * The OTP warning handler may be called from within Send calls
+ * to the interface.
+ *
+ * @param o the object
+ * @return sending interface
+ */
+PacketPassInterface * DatagramPeerIO_GetSendInput (DatagramPeerIO *o);
+
+/**
+ * Attempts to establish connection to the peer which has bound to an address.
+ * On success, the interface enters connecting mode.
+ * On failure, the interface enters default mode.
+ *
+ * @param o the object
+ * @param addr address to send packets to
+ * @return 1 on success, 0 on failure
+ */
+int DatagramPeerIO_Connect (DatagramPeerIO *o, BAddr addr) WARN_UNUSED;
+
+/**
+ * Attempts to establish connection to the peer by binding to an address.
+ * On success, the interface enters connecting mode.
+ * On failure, the interface enters default mode.
+ *
+ * @param o the object
+ * @param addr address to bind to. Must be supported according to
+ *             {@link BDatagram_AddressFamilySupported}.
+ * @return 1 on success, 0 on failure
+ */
+int DatagramPeerIO_Bind (DatagramPeerIO *o, BAddr addr) WARN_UNUSED;
+
+/**
+ * Sets the encryption key to use for sending and receiving.
+ * Encryption must be enabled.
+ *
+ * @param o the object
+ * @param encryption_key key to use
+ */
+void DatagramPeerIO_SetEncryptionKey (DatagramPeerIO *o, uint8_t *encryption_key);
+
+/**
+ * Removed the encryption key to use for sending and receiving.
+ * Encryption must be enabled.
+ *
+ * @param o the object
+ */
+void DatagramPeerIO_RemoveEncryptionKey (DatagramPeerIO *o);
+
+/**
+ * Sets the OTP seed for sending.
+ * OTPs must be enabled.
+ *
+ * @param o the object
+ * @param seed_id seed identifier
+ * @param key OTP encryption key
+ * @param iv OTP initialization vector
+ */
+void DatagramPeerIO_SetOTPSendSeed (DatagramPeerIO *o, uint16_t seed_id, uint8_t *key, uint8_t *iv);
+
+/**
+ * Removes the OTP seed for sending of one is configured.
+ * OTPs must be enabled.
+ *
+ * @param o the object
+ */
+void DatagramPeerIO_RemoveOTPSendSeed (DatagramPeerIO *o);
+
+/**
+ * Adds an OTP seed for reciving.
+ * OTPs must be enabled.
+ *
+ * @param o the object
+ * @param seed_id seed identifier
+ * @param key OTP encryption key
+ * @param iv OTP initialization vector
+ */
+void DatagramPeerIO_AddOTPRecvSeed (DatagramPeerIO *o, uint16_t seed_id, uint8_t *key, uint8_t *iv);
+
+/**
+ * Removes all OTP seeds for reciving.
+ * OTPs must be enabled.
+ *
+ * @param o the object
+ */
+void DatagramPeerIO_RemoveOTPRecvSeeds (DatagramPeerIO *o);
+
+#endif
diff --git a/external/badvpn_dns/client/FragmentProtoAssembler.c b/external/badvpn_dns/client/FragmentProtoAssembler.c
new file mode 100644
index 0000000..8588c2e
--- /dev/null
+++ b/external/badvpn_dns/client/FragmentProtoAssembler.c
@@ -0,0 +1,469 @@
+/**
+ * @file FragmentProtoAssembler.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <misc/offset.h>
+#include <misc/byteorder.h>
+#include <misc/balloc.h>
+
+#include "FragmentProtoAssembler.h"
+
+#include <generated/blog_channel_FragmentProtoAssembler.h>
+
+#define PeerLog(_o, ...) BLog_LogViaFunc((_o)->logfunc, (_o)->user, BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+#include "FragmentProtoAssembler_tree.h"
+#include <structure/SAvl_impl.h>
+
+static void free_frame (FragmentProtoAssembler *o, struct FragmentProtoAssembler_frame *frame)
+{
+    // remove from used list
+    LinkedList1_Remove(&o->frames_used, &frame->list_node);
+    // remove from used tree
+    FPAFramesTree_Remove(&o->frames_used_tree, 0, frame);
+    
+    // append to free list
+    LinkedList1_Append(&o->frames_free, &frame->list_node);
+}
+
+static void free_oldest_frame (FragmentProtoAssembler *o)
+{
+    ASSERT(!LinkedList1_IsEmpty(&o->frames_used))
+    
+    // obtain oldest frame (first on the list)
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->frames_used);
+    ASSERT(list_node)
+    struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node);
+    
+    // free frame
+    free_frame(o, frame);
+}
+
+static struct FragmentProtoAssembler_frame * allocate_new_frame (FragmentProtoAssembler *o, fragmentproto_frameid id)
+{
+    ASSERT(!FPAFramesTree_LookupExact(&o->frames_used_tree, 0, id))
+    
+    // if there are no free entries, free the oldest used one
+    if (LinkedList1_IsEmpty(&o->frames_free)) {
+        PeerLog(o, BLOG_INFO, "freeing used frame");
+        free_oldest_frame(o);
+    }
+    
+    // obtain frame entry
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->frames_free);
+    ASSERT(list_node)
+    struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node);
+    
+    // remove from free list
+    LinkedList1_Remove(&o->frames_free, &frame->list_node);
+    
+    // initialize values
+    frame->id = id;
+    frame->time = o->time;
+    frame->num_chunks = 0;
+    frame->sum = 0;
+    frame->length = -1;
+    frame->length_so_far = 0;
+    
+    // append to used list
+    LinkedList1_Append(&o->frames_used, &frame->list_node);
+    // insert to used tree
+    int res = FPAFramesTree_Insert(&o->frames_used_tree, 0, frame, NULL);
+    ASSERT_EXECUTE(res)
+    
+    return frame;
+}
+
+static int chunks_overlap (int c1_start, int c1_len, int c2_start, int c2_len)
+{
+    return (c1_start + c1_len > c2_start && c2_start + c2_len > c1_start);
+}
+
+static int frame_is_timed_out (FragmentProtoAssembler *o, struct FragmentProtoAssembler_frame *frame)
+{
+    ASSERT(frame->time <= o->time)
+    
+    return (o->time - frame->time > o->time_tolerance);
+}
+
+static void reduce_times (FragmentProtoAssembler *o)
+{
+    // find the frame with minimal time, removing timed out frames
+    struct FragmentProtoAssembler_frame *minframe = NULL;
+    LinkedList1Node *list_node = LinkedList1_GetFirst(&o->frames_used);
+    while (list_node) {
+        LinkedList1Node *next = LinkedList1Node_Next(list_node);
+        struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node);
+        if (frame_is_timed_out(o, frame)) {
+            PeerLog(o, BLOG_INFO, "freeing timed out frame (while reducing times)");
+            free_frame(o, frame);
+        } else {
+            if (!minframe || frame->time < minframe->time) {
+                minframe = frame;
+            }
+        }
+        list_node = next;
+    }
+    
+    if (!minframe) {
+        // have no frames, set packet time to zero
+        o->time = 0;
+        return;
+    }
+    
+    uint32_t min_time = minframe->time;
+    
+    // subtract minimal time from all frames
+    for (list_node = LinkedList1_GetFirst(&o->frames_used); list_node; list_node = LinkedList1Node_Next(list_node)) {
+        struct FragmentProtoAssembler_frame *frame = UPPER_OBJECT(list_node, struct FragmentProtoAssembler_frame, list_node);
+        frame->time -= min_time;
+    }
+    
+    // subtract minimal time from packet time
+    o->time -= min_time;
+}
+
+static int process_chunk (FragmentProtoAssembler *o, fragmentproto_frameid frame_id, int chunk_start, int chunk_len, int is_last, uint8_t *payload)
+{
+    ASSERT(chunk_start >= 0)
+    ASSERT(chunk_len >= 0)
+    ASSERT(is_last == 0 || is_last == 1)
+    
+    // verify chunk
+    
+    // check start
+    if (chunk_start > o->output_mtu) {
+        PeerLog(o, BLOG_INFO, "chunk starts outside");
+        return 0;
+    }
+    
+    // check frame size bound
+    if (chunk_len > o->output_mtu - chunk_start) {
+        PeerLog(o, BLOG_INFO, "chunk ends outside");
+        return 0;
+    }
+    
+    // calculate end
+    int chunk_end = chunk_start + chunk_len;
+    ASSERT(chunk_end >= 0)
+    ASSERT(chunk_end <= o->output_mtu)
+    
+    // lookup frame
+    struct FragmentProtoAssembler_frame *frame = FPAFramesTree_LookupExact(&o->frames_used_tree, 0, frame_id);
+    if (!frame) {
+        // frame not found, add a new one
+        frame = allocate_new_frame(o, frame_id);
+    } else {
+        // have existing frame with that ID
+        // check frame time
+        if (frame_is_timed_out(o, frame)) {
+            // frame is timed out, remove it and use a new one
+            PeerLog(o, BLOG_INFO, "freeing timed out frame (while processing chunk)");
+            free_frame(o, frame);
+            frame = allocate_new_frame(o, frame_id);
+        }
+    }
+    
+    ASSERT(frame->num_chunks < o->num_chunks)
+    
+    // check if the chunk overlaps with any existing chunks
+    for (int i = 0; i < frame->num_chunks; i++) {
+        struct FragmentProtoAssembler_chunk *chunk = &frame->chunks[i];
+        if (chunks_overlap(chunk->start, chunk->len, chunk_start, chunk_len)) {
+            PeerLog(o, BLOG_INFO, "chunk overlaps with existing chunk");
+            goto fail_frame;
+        }
+    }
+    
+    if (is_last) {
+        // this chunk is marked as last
+        if (frame->length >= 0) {
+            PeerLog(o, BLOG_INFO, "got last chunk, but already have one");
+            goto fail_frame;
+        }
+        // check if frame size according to this packet is consistent
+        // with existing chunks
+        if (frame->length_so_far > chunk_end) {
+            PeerLog(o, BLOG_INFO, "got last chunk, but already have data over its bound");
+            goto fail_frame;
+        }
+    } else {
+        // if we have length, chunk must be in its bound
+        if (frame->length >= 0) {
+            if (chunk_end > frame->length) {
+                PeerLog(o, BLOG_INFO, "chunk out of length bound");
+                goto fail_frame;
+            }
+        }
+    }
+    
+    // chunk is good, add it
+    
+    // update frame time
+    frame->time = o->time;
+    
+    // add chunk entry
+    struct FragmentProtoAssembler_chunk *chunk = &frame->chunks[frame->num_chunks];
+    chunk->start = chunk_start;
+    chunk->len = chunk_len;
+    frame->num_chunks++;
+    
+    // update sum
+    frame->sum += chunk_len;
+    
+    // update length
+    if (is_last) {
+        frame->length = chunk_end;
+    } else {
+        if (frame->length < 0) {
+            if (frame->length_so_far < chunk_end) {
+                frame->length_so_far = chunk_end;
+            }
+        }
+    }
+    
+    // copy chunk payload to buffer
+    memcpy(frame->buffer + chunk_start, payload, chunk_len);
+    
+    // is frame incomplete?
+    if (frame->length < 0 || frame->sum < frame->length) {
+        // if all chunks are used, fail it
+        if (frame->num_chunks == o->num_chunks) {
+            PeerLog(o, BLOG_INFO, "all chunks used, but frame not complete");
+            goto fail_frame;
+        }
+        
+        // wait for more chunks
+        return 0;
+    }
+    
+    ASSERT(frame->sum == frame->length)
+    
+    PeerLog(o, BLOG_DEBUG, "frame complete");
+    
+    // free frame entry
+    free_frame(o, frame);
+    
+    // send frame
+    PacketPassInterface_Sender_Send(o->output, frame->buffer, frame->length);
+    
+    return 1;
+    
+fail_frame:
+    free_frame(o, frame);
+    return 0;
+}
+
+static void process_input (FragmentProtoAssembler *o)
+{
+    ASSERT(o->in_len >= 0)
+    
+    // read chunks
+    while (o->in_pos < o->in_len) {
+        // obtain header
+        if (o->in_len - o->in_pos < sizeof(struct fragmentproto_chunk_header)) {
+            PeerLog(o, BLOG_INFO, "too little data for chunk header");
+            break;
+        }
+        struct fragmentproto_chunk_header header;
+        memcpy(&header, o->in + o->in_pos, sizeof(header));
+        o->in_pos += sizeof(struct fragmentproto_chunk_header);
+        fragmentproto_frameid frame_id = ltoh16(header.frame_id);
+        int chunk_start = ltoh16(header.chunk_start);
+        int chunk_len = ltoh16(header.chunk_len);
+        int is_last = ltoh8(header.is_last);
+        
+        // check is_last field
+        if (!(is_last == 0 || is_last == 1)) {
+            PeerLog(o, BLOG_INFO, "chunk is_last wrong");
+            break;
+        }
+        
+        // obtain data
+        if (o->in_len - o->in_pos < chunk_len) {
+            PeerLog(o, BLOG_INFO, "too little data for chunk data");
+            break;
+        }
+        
+        // process chunk
+        int res = process_chunk(o, frame_id, chunk_start, chunk_len, is_last, o->in + o->in_pos);
+        o->in_pos += chunk_len;
+        
+        if (res) {
+            // sending complete frame, stop processing input
+            return;
+        }
+    }
+    
+    // increment packet time
+    if (o->time == FPA_MAX_TIME) {
+        reduce_times(o);
+        if (!LinkedList1_IsEmpty(&o->frames_used)) {
+            ASSERT(o->time < FPA_MAX_TIME) // If there was a frame with zero time, it was removed because
+                                           // time_tolerance < FPA_MAX_TIME. So something >0 was subtracted.
+            o->time++;
+        } else {
+            // it was set to zero by reduce_times
+            ASSERT(o->time == 0)
+        }
+    } else {
+        o->time++;
+    }
+    
+    // set no input packet
+    o->in_len = -1;
+    
+    // finish input
+    PacketPassInterface_Done(&o->input);
+}
+
+static void input_handler_send (FragmentProtoAssembler *o, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(o->in_len == -1)
+    DebugObject_Access(&o->d_obj);
+    
+    // save input packet
+    o->in_len = data_len;
+    o->in = data;
+    o->in_pos = 0;
+    
+    process_input(o);
+}
+
+static void output_handler_done (FragmentProtoAssembler *o)
+{
+    ASSERT(o->in_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    process_input(o);
+}
+
+int FragmentProtoAssembler_Init (FragmentProtoAssembler *o, int input_mtu, PacketPassInterface *output, int num_frames, int num_chunks, BPendingGroup *pg, void *user, BLog_logfunc logfunc)
+{
+    ASSERT(input_mtu >= 0)
+    ASSERT(num_frames > 0)
+    ASSERT(num_frames < FPA_MAX_TIME) // needed so we can always subtract times when packet time is maximum
+    ASSERT(num_chunks > 0)
+    
+    // init arguments
+    o->output = output;
+    o->num_chunks = num_chunks;
+    o->user = user;
+    o->logfunc = logfunc;
+    
+    // init input
+    PacketPassInterface_Init(&o->input, input_mtu, (PacketPassInterface_handler_send)input_handler_send, o, pg);
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // remebmer output MTU
+    o->output_mtu = PacketPassInterface_GetMTU(o->output);
+    
+    // set packet time to zero
+    o->time = 0;
+    
+    // set time tolerance to num_frames
+    o->time_tolerance = num_frames;
+    
+    // allocate frames
+    if (!(o->frames_entries = (struct FragmentProtoAssembler_frame *)BAllocArray(num_frames, sizeof(o->frames_entries[0])))) {
+        goto fail1;
+    }
+    
+    // allocate chunks
+    if (!(o->frames_chunks = (struct FragmentProtoAssembler_chunk *)BAllocArray2(num_frames, o->num_chunks, sizeof(o->frames_chunks[0])))) {
+        goto fail2;
+    }
+    
+    // allocate buffers
+    if (!(o->frames_buffer = (uint8_t *)BAllocArray(num_frames, o->output_mtu))) {
+        goto fail3;
+    }
+    
+    // init frame lists
+    LinkedList1_Init(&o->frames_free);
+    LinkedList1_Init(&o->frames_used);
+    
+    // initialize frame entries
+    for (int i = 0; i < num_frames; i++) {
+        struct FragmentProtoAssembler_frame *frame = &o->frames_entries[i];
+        // set chunks array pointer
+        frame->chunks = o->frames_chunks + (size_t)i * o->num_chunks;
+        // set buffer pointer
+        frame->buffer = o->frames_buffer + (size_t)i * o->output_mtu;
+        // add to free list
+        LinkedList1_Append(&o->frames_free, &frame->list_node);
+    }
+    
+    // init tree
+    FPAFramesTree_Init(&o->frames_used_tree);
+    
+    // have no input packet
+    o->in_len = -1;
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail3:
+    BFree(o->frames_chunks);
+fail2:
+    BFree(o->frames_entries);
+fail1:
+    PacketPassInterface_Free(&o->input);
+    return 0;
+}
+
+void FragmentProtoAssembler_Free (FragmentProtoAssembler *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free buffers
+    BFree(o->frames_buffer);
+    
+    // free chunks
+    BFree(o->frames_chunks);
+    
+    // free frames
+    BFree(o->frames_entries);
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+PacketPassInterface * FragmentProtoAssembler_GetInput (FragmentProtoAssembler *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
diff --git a/external/badvpn_dns/client/FragmentProtoAssembler.h b/external/badvpn_dns/client/FragmentProtoAssembler.h
new file mode 100644
index 0000000..bbc5483
--- /dev/null
+++ b/external/badvpn_dns/client/FragmentProtoAssembler.h
@@ -0,0 +1,134 @@
+/**
+ * @file FragmentProtoAssembler.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object which decodes packets according to FragmentProto.
+ */
+
+#ifndef BADVPN_CLIENT_FRAGMENTPROTOASSEMBLER_H
+#define BADVPN_CLIENT_FRAGMENTPROTOASSEMBLER_H
+
+#include <stdint.h>
+
+#include <protocol/fragmentproto.h>
+#include <misc/debug.h>
+#include <misc/compare.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <structure/LinkedList1.h>
+#include <structure/SAvl.h>
+#include <flow/PacketPassInterface.h>
+
+#define FPA_MAX_TIME UINT32_MAX
+
+struct FragmentProtoAssembler_frame;
+
+#include "FragmentProtoAssembler_tree.h"
+#include <structure/SAvl_decl.h>
+
+struct FragmentProtoAssembler_chunk {
+    int start;
+    int len;
+};
+
+struct FragmentProtoAssembler_frame {
+    LinkedList1Node list_node; // node in free or used list
+    struct FragmentProtoAssembler_chunk *chunks; // array of chunks, up to num_chunks
+    uint8_t *buffer; // buffer with frame data, size output_mtu
+    // everything below only defined when frame entry is used
+    fragmentproto_frameid id; // frame identifier
+    uint32_t time; // packet time when the last chunk was received
+    FPAFramesTreeNode tree_node; // node fields in tree for searching frames by id
+    int num_chunks; // number of valid chunks
+    int sum; // sum of all chunks' lengths
+    int length; // length of the frame, or -1 if not yet known
+    int length_so_far; // if length=-1, current data set's upper bound
+};
+
+/**
+ * Object which decodes packets according to FragmentProto.
+ *
+ * Input is with {@link PacketPassInterface}.
+ * Output is with {@link PacketPassInterface}.
+ */
+typedef struct {
+    void *user;
+    BLog_logfunc logfunc;
+    PacketPassInterface input;
+    PacketPassInterface *output;
+    int output_mtu;
+    int num_chunks;
+    uint32_t time;
+    int time_tolerance;
+    struct FragmentProtoAssembler_frame *frames_entries;
+    struct FragmentProtoAssembler_chunk *frames_chunks;
+    uint8_t *frames_buffer;
+    LinkedList1 frames_free;
+    LinkedList1 frames_used;
+    FPAFramesTree frames_used_tree;
+    int in_len;
+    uint8_t *in;
+    int in_pos;
+    DebugObject d_obj;
+} FragmentProtoAssembler;
+
+/**
+ * Initializes the object.
+ * {@link BLog_Init} must have been done.
+ *
+ * @param o the object
+ * @param input_mtu maximum input packet size. Must be >=0.
+ * @param output output interface
+ * @param num_frames number of frames we can hold. Must be >0 and < FPA_MAX_TIME.
+ *  To make the assembler tolerate out-of-order input of degree D, set to D+2.
+ *  Here, D is the minimum size of a hypothetical buffer needed to order the input.
+ * @param num_chunks maximum number of chunks a frame can come in. Must be >0.
+ * @param pg pending group
+ * @param user argument to handlers
+ * @param logfunc function which prepends the log prefix using {@link BLog_Append}
+ * @return 1 on success, 0 on failure
+ */
+int FragmentProtoAssembler_Init (FragmentProtoAssembler *o, int input_mtu, PacketPassInterface *output, int num_frames, int num_chunks, BPendingGroup *pg, void *user, BLog_logfunc logfunc) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void FragmentProtoAssembler_Free (FragmentProtoAssembler *o);
+
+/**
+ * Returns the input interface.
+ *
+ * @param o the object
+ * @return input interface
+ */
+PacketPassInterface * FragmentProtoAssembler_GetInput (FragmentProtoAssembler *o);
+
+#endif
diff --git a/external/badvpn_dns/client/FragmentProtoAssembler_tree.h b/external/badvpn_dns/client/FragmentProtoAssembler_tree.h
new file mode 100644
index 0000000..744c633
--- /dev/null
+++ b/external/badvpn_dns/client/FragmentProtoAssembler_tree.h
@@ -0,0 +1,9 @@
+#define SAVL_PARAM_NAME FPAFramesTree
+#define SAVL_PARAM_FEATURE_COUNTS 0
+#define SAVL_PARAM_FEATURE_NOKEYS 0
+#define SAVL_PARAM_TYPE_ENTRY struct FragmentProtoAssembler_frame
+#define SAVL_PARAM_TYPE_KEY fragmentproto_frameid
+#define SAVL_PARAM_TYPE_ARG int
+#define SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) B_COMPARE((entry1)->id, (entry2)->id)
+#define SAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) B_COMPARE((key1), (entry2)->id)
+#define SAVL_PARAM_MEMBER_NODE tree_node
diff --git a/external/badvpn_dns/client/FragmentProtoDisassembler.c b/external/badvpn_dns/client/FragmentProtoDisassembler.c
new file mode 100644
index 0000000..e67a1dc
--- /dev/null
+++ b/external/badvpn_dns/client/FragmentProtoDisassembler.c
@@ -0,0 +1,229 @@
+/**
+ * @file FragmentProtoDisassembler.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/minmax.h>
+
+#include "client/FragmentProtoDisassembler.h"
+
+static void write_chunks (FragmentProtoDisassembler *o)
+{
+    #define IN_AVAIL (o->in_len - o->in_used)
+    #define OUT_AVAIL ((o->output_mtu - o->out_used) - (int)sizeof(struct fragmentproto_chunk_header))
+    
+    ASSERT(o->in_len >= 0)
+    ASSERT(o->out)
+    ASSERT(OUT_AVAIL > 0)
+    
+    // write chunks to output packet
+    do {
+        // calculate chunk length
+        int chunk_len = bmin_int(IN_AVAIL, OUT_AVAIL);
+        if (o->chunk_mtu > 0) {
+            chunk_len = bmin_int(chunk_len, o->chunk_mtu);
+        }
+        
+        // write chunk header
+        struct fragmentproto_chunk_header header;
+        header.frame_id = htol16(o->frame_id);
+        header.chunk_start = htol16(o->in_used);
+        header.chunk_len = htol16(chunk_len);
+        header.is_last = (chunk_len == IN_AVAIL);
+        memcpy(o->out + o->out_used, &header, sizeof(header));
+        
+        // write chunk data
+        memcpy(o->out + o->out_used + sizeof(struct fragmentproto_chunk_header), o->in + o->in_used, chunk_len);
+        
+        // increment pointers
+        o->in_used += chunk_len;
+        o->out_used += sizeof(struct fragmentproto_chunk_header) + chunk_len;
+    } while (IN_AVAIL > 0 && OUT_AVAIL > 0);
+    
+    // have we finished the input packet?
+    if (IN_AVAIL == 0) {
+        // set no input packet
+        o->in_len = -1;
+        
+        // increment frame ID
+        o->frame_id++;
+        
+        // finish input
+        PacketPassInterface_Done(&o->input);
+    }
+    
+    // should we finish the output packet?
+    if (OUT_AVAIL <= 0 || o->latency < 0) {
+        // set no output packet
+        o->out = NULL;
+        
+        // stop timer (if it's running)
+        if (o->latency >= 0) {
+            BReactor_RemoveTimer(o->reactor, &o->timer);
+        }
+        
+        // finish output
+        PacketRecvInterface_Done(&o->output, o->out_used);
+    } else {
+        // start timer if we have output and it's not running (output was empty before)
+        if (!BTimer_IsRunning(&o->timer)) {
+            BReactor_SetTimer(o->reactor, &o->timer);
+        }
+    }
+}
+
+static void input_handler_send (FragmentProtoDisassembler *o, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(o->in_len == -1)
+    
+    // set input packet
+    o->in_len = data_len;
+    o->in = data;
+    o->in_used = 0;
+    
+    // if there is no output, wait for it
+    if (!o->out) {
+        return;
+    }
+    
+    write_chunks(o);
+}
+
+static void input_handler_requestcancel (FragmentProtoDisassembler *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(!o->out)
+    
+    // set no input packet
+    o->in_len = -1;
+    
+    // finish input
+    PacketPassInterface_Done(&o->input);
+}
+
+static void output_handler_recv (FragmentProtoDisassembler *o, uint8_t *data)
+{
+    ASSERT(data)
+    ASSERT(!o->out)
+    
+    // set output packet
+    o->out = data;
+    o->out_used = 0;
+    
+    // if there is no input, wait for it
+    if (o->in_len < 0) {
+        return;
+    }
+    
+    write_chunks(o);
+}
+
+static void timer_handler (FragmentProtoDisassembler *o)
+{
+    ASSERT(o->latency >= 0)
+    ASSERT(o->out)
+    ASSERT(o->in_len == -1)
+    
+    // set no output packet
+    o->out = NULL;
+    
+    // finish output
+    PacketRecvInterface_Done(&o->output, o->out_used);
+}
+
+void FragmentProtoDisassembler_Init (FragmentProtoDisassembler *o, BReactor *reactor, int input_mtu, int output_mtu, int chunk_mtu, btime_t latency)
+{
+    ASSERT(input_mtu >= 0)
+    ASSERT(input_mtu <= UINT16_MAX)
+    ASSERT(output_mtu > sizeof(struct fragmentproto_chunk_header))
+    ASSERT(chunk_mtu > 0 || chunk_mtu < 0)
+    
+    // init arguments
+    o->reactor = reactor;
+    o->output_mtu = output_mtu;
+    o->chunk_mtu = chunk_mtu;
+    o->latency = latency;
+    
+    // init input
+    PacketPassInterface_Init(&o->input, input_mtu, (PacketPassInterface_handler_send)input_handler_send, o, BReactor_PendingGroup(reactor));
+    PacketPassInterface_EnableCancel(&o->input, (PacketPassInterface_handler_requestcancel)input_handler_requestcancel);
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, o->output_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o, BReactor_PendingGroup(reactor));
+    
+    // init timer
+    if (o->latency >= 0) {
+        BTimer_Init(&o->timer, o->latency, (BTimer_handler)timer_handler, o);
+    }
+    
+    // have no input packet
+    o->in_len = -1;
+    
+    // have no output packet
+    o->out = NULL;
+    
+    // start with zero frame ID
+    o->frame_id = 0;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void FragmentProtoDisassembler_Free (FragmentProtoDisassembler *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free timer
+    if (o->latency >= 0) {
+        BReactor_RemoveTimer(o->reactor, &o->timer);
+    }
+    
+    // free output
+    PacketRecvInterface_Free(&o->output);
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+PacketPassInterface * FragmentProtoDisassembler_GetInput (FragmentProtoDisassembler *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
+
+PacketRecvInterface * FragmentProtoDisassembler_GetOutput (FragmentProtoDisassembler *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
diff --git a/external/badvpn_dns/client/FragmentProtoDisassembler.h b/external/badvpn_dns/client/FragmentProtoDisassembler.h
new file mode 100644
index 0000000..49fe9c8
--- /dev/null
+++ b/external/badvpn_dns/client/FragmentProtoDisassembler.h
@@ -0,0 +1,109 @@
+/**
+ * @file FragmentProtoDisassembler.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object which encodes packets into packets composed of chunks
+ * according to FragmentProto.
+ */
+
+#ifndef BADVPN_CLIENT_CCPROTODISASSEMBLER_H
+#define BADVPN_CLIENT_CCPROTODISASSEMBLER_H
+
+#include <stdint.h>
+
+#include <protocol/fragmentproto.h>
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <system/BTime.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * Object which encodes packets into packets composed of chunks
+ * according to FragmentProto.
+ *
+ * Input is with {@link PacketPassInterface}.
+ * Output is with {@link PacketRecvInterface}.
+ */
+typedef struct {
+    BReactor *reactor;
+    int output_mtu;
+    int chunk_mtu;
+    btime_t latency;
+    PacketPassInterface input;
+    PacketRecvInterface output;
+    BTimer timer;
+    int in_len;
+    uint8_t *in;
+    int in_used;
+    uint8_t *out;
+    int out_used;
+    fragmentproto_frameid frame_id;
+    DebugObject d_obj;
+} FragmentProtoDisassembler;
+
+/**
+ * Initializes the object.
+ *
+ * @param o the object
+ * @param reactor reactor we live in
+ * @param input_mtu maximum input packet size. Must be >=0 and <=UINT16_MAX.
+ * @param output_mtu maximum output packet size. Must be >sizeof(struct fragmentproto_chunk_header).
+ * @param chunk_mtu maximum chunk size. Must be >0, or <0 for no explicit limit.
+ * @param latency maximum time a pending output packet with some data can wait for more data
+ *                before being sent out. If nonnegative, a timer will be used. If negative,
+ *                packets will always be sent out immediately. If low latency is desired,
+ *                prefer setting this to zero rather than negative.
+ */
+void FragmentProtoDisassembler_Init (FragmentProtoDisassembler *o, BReactor *reactor, int input_mtu, int output_mtu, int chunk_mtu, btime_t latency);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void FragmentProtoDisassembler_Free (FragmentProtoDisassembler *o);
+
+/**
+ * Returns the input interface.
+ *
+ * @param o the object
+ * @return input interface
+ */
+PacketPassInterface * FragmentProtoDisassembler_GetInput (FragmentProtoDisassembler *o);
+
+/**
+ * Returns the output interface.
+ *
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * FragmentProtoDisassembler_GetOutput (FragmentProtoDisassembler *o);
+
+#endif
diff --git a/external/badvpn_dns/client/FrameDecider.c b/external/badvpn_dns/client/FrameDecider.c
new file mode 100644
index 0000000..e7bb4de
--- /dev/null
+++ b/external/badvpn_dns/client/FrameDecider.c
@@ -0,0 +1,795 @@
+/**
+ * @file FrameDecider.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/balloc.h>
+#include <misc/ethernet_proto.h>
+#include <misc/ipv4_proto.h>
+#include <misc/igmp_proto.h>
+#include <misc/byteorder.h>
+#include <misc/compare.h>
+#include <misc/print_macros.h>
+
+#include <client/FrameDecider.h>
+
+#include <generated/blog_channel_FrameDecider.h>
+
+#define DECIDE_STATE_NONE 1
+#define DECIDE_STATE_UNICAST 2
+#define DECIDE_STATE_FLOOD 3
+#define DECIDE_STATE_MULTICAST 4
+
+#define PeerLog(_o, ...) BLog_LogViaFunc((_o)->logfunc, (_o)->user, BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static int compare_macs (const uint8_t *mac1, const uint8_t *mac2)
+{
+    int c = memcmp(mac1, mac2, 6);
+    return B_COMPARE(c, 0);
+}
+
+#include "FrameDecider_macs_tree.h"
+#include <structure/SAvl_impl.h>
+
+#include "FrameDecider_groups_tree.h"
+#include <structure/SAvl_impl.h>
+
+#include "FrameDecider_multicast_tree.h"
+#include <structure/SAvl_impl.h>
+
+static void add_mac_to_peer (FrameDeciderPeer *o, uint8_t *mac)
+{
+    FrameDecider *d = o->d;
+    
+    // locate entry in tree
+    struct _FrameDecider_mac_entry *e_entry = FDMacsTree_LookupExact(&d->macs_tree, 0, mac);
+    if (e_entry) {
+        if (e_entry->peer == o) {
+            // this is our MAC; only move it to the end of the used list
+            LinkedList1_Remove(&o->mac_entries_used, &e_entry->list_node);
+            LinkedList1_Append(&o->mac_entries_used, &e_entry->list_node);
+            return;
+        }
+        
+        // some other peer has that MAC; disassociate it
+        FDMacsTree_Remove(&d->macs_tree, 0, e_entry);
+        LinkedList1_Remove(&e_entry->peer->mac_entries_used, &e_entry->list_node);
+        LinkedList1_Append(&e_entry->peer->mac_entries_free, &e_entry->list_node);
+    }
+    
+    // aquire MAC address entry, if there are no free ones reuse the oldest used one
+    LinkedList1Node *list_node;
+    struct _FrameDecider_mac_entry *entry;
+    if (list_node = LinkedList1_GetFirst(&o->mac_entries_free)) {
+        entry = UPPER_OBJECT(list_node, struct _FrameDecider_mac_entry, list_node);
+        ASSERT(entry->peer == o)
+        
+        // remove from free
+        LinkedList1_Remove(&o->mac_entries_free, &entry->list_node);
+    } else {
+        list_node = LinkedList1_GetFirst(&o->mac_entries_used);
+        ASSERT(list_node)
+        entry = UPPER_OBJECT(list_node, struct _FrameDecider_mac_entry, list_node);
+        ASSERT(entry->peer == o)
+        
+        // remove from used
+        FDMacsTree_Remove(&d->macs_tree, 0, entry);
+        LinkedList1_Remove(&o->mac_entries_used, &entry->list_node);
+    }
+    
+    PeerLog(o, BLOG_INFO, "adding MAC %02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8"", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+    
+    // set MAC in entry
+    memcpy(entry->mac, mac, sizeof(entry->mac));
+    
+    // add to used
+    LinkedList1_Append(&o->mac_entries_used, &entry->list_node);
+    int res = FDMacsTree_Insert(&d->macs_tree, 0, entry, NULL);
+    ASSERT_EXECUTE(res)
+}
+
+static uint32_t compute_sig_for_group (uint32_t group)
+{
+    return hton32(ntoh32(group)&0x7FFFFF);
+}
+
+static uint32_t compute_sig_for_mac (uint8_t *mac)
+{
+    uint32_t sig;
+    memcpy(&sig, mac + 2, 4);
+    sig = hton32(ntoh32(sig)&0x7FFFFF);
+    return sig;
+}
+
+static void add_to_multicast (FrameDecider *d, struct _FrameDecider_group_entry *group_entry)
+{
+    // compute sig
+    uint32_t sig = compute_sig_for_group(group_entry->group);
+    
+    struct _FrameDecider_group_entry *master = FDMulticastTree_LookupExact(&d->multicast_tree, 0, sig);
+    if (master) {
+        // use existing master
+        ASSERT(master->is_master)
+        
+        // set not master
+        group_entry->is_master = 0;
+        
+        // insert to list
+        LinkedList3Node_InitAfter(&group_entry->sig_list_node, &master->sig_list_node);
+    } else {
+        // make this entry master
+        
+        // set master
+        group_entry->is_master = 1;
+        
+        // set sig
+        group_entry->master.sig = sig;
+        
+        // insert to multicast tree
+        int res = FDMulticastTree_Insert(&d->multicast_tree, 0, group_entry, NULL);
+        ASSERT_EXECUTE(res)
+        
+        // init list node
+        LinkedList3Node_InitLonely(&group_entry->sig_list_node);
+    }
+}
+
+static void remove_from_multicast (FrameDecider *d, struct _FrameDecider_group_entry *group_entry)
+{
+    // compute sig
+    uint32_t sig = compute_sig_for_group(group_entry->group);
+    
+    if (group_entry->is_master) {
+        // remove master from multicast tree
+        FDMulticastTree_Remove(&d->multicast_tree, 0, group_entry);
+        
+        if (!LinkedList3Node_IsLonely(&group_entry->sig_list_node)) {
+            // at least one more group entry for this sig; make another entry the master
+            
+            // get an entry
+            LinkedList3Node *list_node = LinkedList3Node_NextOrPrev(&group_entry->sig_list_node);
+            struct _FrameDecider_group_entry *newmaster = UPPER_OBJECT(list_node, struct _FrameDecider_group_entry, sig_list_node);
+            ASSERT(!newmaster->is_master)
+            
+            // set master
+            newmaster->is_master = 1;
+            
+            // set sig
+            newmaster->master.sig = sig;
+            
+            // insert to multicast tree
+            int res = FDMulticastTree_Insert(&d->multicast_tree, 0, newmaster, NULL);
+            ASSERT_EXECUTE(res)
+        }
+    }
+    
+    // free linked list node
+    LinkedList3Node_Free(&group_entry->sig_list_node);
+}
+
+static void add_group_to_peer (FrameDeciderPeer *o, uint32_t group)
+{
+    FrameDecider *d = o->d;
+    
+    struct _FrameDecider_group_entry *group_entry = FDGroupsTree_LookupExact(&o->groups_tree, 0, group);
+    if (group_entry) {
+        // move to end of used list
+        LinkedList1_Remove(&o->group_entries_used, &group_entry->list_node);
+        LinkedList1_Append(&o->group_entries_used, &group_entry->list_node);
+    } else {
+        PeerLog(o, BLOG_INFO, "joined group %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"",
+            ((uint8_t *)&group)[0], ((uint8_t *)&group)[1], ((uint8_t *)&group)[2], ((uint8_t *)&group)[3]
+        );
+        
+        // aquire group entry, if there are no free ones reuse the earliest used one
+        LinkedList1Node *node;
+        if (node = LinkedList1_GetFirst(&o->group_entries_free)) {
+            group_entry = UPPER_OBJECT(node, struct _FrameDecider_group_entry, list_node);
+            
+            // remove from free list
+            LinkedList1_Remove(&o->group_entries_free, &group_entry->list_node);
+        } else {
+            node = LinkedList1_GetFirst(&o->group_entries_used);
+            ASSERT(node)
+            group_entry = UPPER_OBJECT(node, struct _FrameDecider_group_entry, list_node);
+            
+            // remove from multicast
+            remove_from_multicast(d, group_entry);
+            
+            // remove from peer's groups tree
+            FDGroupsTree_Remove(&o->groups_tree, 0, group_entry);
+            
+            // remove from used list
+            LinkedList1_Remove(&o->group_entries_used, &group_entry->list_node);
+        }
+        
+        // add entry to used list
+        LinkedList1_Append(&o->group_entries_used, &group_entry->list_node);
+        
+        // set group address
+        group_entry->group = group;
+        
+        // insert to peer's groups tree
+        int res = FDGroupsTree_Insert(&o->groups_tree, 0, group_entry, NULL);
+        ASSERT_EXECUTE(res)
+        
+        // add to multicast
+        add_to_multicast(d, group_entry);
+    }
+    
+    // set timer
+    group_entry->timer_endtime = btime_gettime() + d->igmp_group_membership_interval;
+    BReactor_SetTimerAbsolute(d->reactor, &group_entry->timer, group_entry->timer_endtime);
+}
+
+static void remove_group_entry (struct _FrameDecider_group_entry *group_entry)
+{
+    FrameDeciderPeer *peer = group_entry->peer;
+    FrameDecider *d = peer->d;
+    
+    uint32_t group = group_entry->group;
+    
+    PeerLog(peer, BLOG_INFO, "left group %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8"",
+        ((uint8_t *)&group)[0], ((uint8_t *)&group)[1], ((uint8_t *)&group)[2], ((uint8_t *)&group)[3]
+    );
+    
+    // remove from multicast
+    remove_from_multicast(d, group_entry);
+    
+    // remove from peer's groups tree
+    FDGroupsTree_Remove(&peer->groups_tree, 0, group_entry);
+    
+    // remove from used list
+    LinkedList1_Remove(&peer->group_entries_used, &group_entry->list_node);
+    
+    // add to free list
+    LinkedList1_Append(&peer->group_entries_free, &group_entry->list_node);
+    
+    // stop timer
+    BReactor_RemoveTimer(d->reactor, &group_entry->timer);
+}
+
+static void lower_group_timers_to_lmqt (FrameDecider *d, uint32_t group)
+{
+    // have to lower all the group timers of this group down to LMQT
+    
+    // compute sig
+    uint32_t sig = compute_sig_for_group(group);
+    
+    // look up the sig in multicast tree
+    struct _FrameDecider_group_entry *master = FDMulticastTree_LookupExact(&d->multicast_tree, 0, sig);
+    if (!master) {
+        return;
+    }
+    ASSERT(master->is_master)
+    
+    // iterate all group entries with this sig
+    LinkedList3Iterator it;
+    LinkedList3Iterator_Init(&it, LinkedList3Node_First(&master->sig_list_node), 1);
+    LinkedList3Node *sig_list_node;
+    while (sig_list_node = LinkedList3Iterator_Next(&it)) {
+        struct _FrameDecider_group_entry *group_entry = UPPER_OBJECT(sig_list_node, struct _FrameDecider_group_entry, sig_list_node);
+        
+        // skip wrong groups
+        if (group_entry->group != group) {
+            continue;
+        }
+        
+        // lower timer down to LMQT
+        btime_t now = btime_gettime();
+        if (group_entry->timer_endtime > now + d->igmp_last_member_query_time) {
+            group_entry->timer_endtime = now + d->igmp_last_member_query_time;
+            BReactor_SetTimerAbsolute(d->reactor, &group_entry->timer, group_entry->timer_endtime);
+        }
+    }
+}
+
+static void group_entry_timer_handler (struct _FrameDecider_group_entry *group_entry)
+{
+    DebugObject_Access(&group_entry->peer->d_obj);
+    
+    remove_group_entry(group_entry);
+}
+
+void FrameDecider_Init (FrameDecider *o, int max_peer_macs, int max_peer_groups, btime_t igmp_group_membership_interval, btime_t igmp_last_member_query_time, BReactor *reactor)
+{
+    ASSERT(max_peer_macs > 0)
+    ASSERT(max_peer_groups > 0)
+    
+    // init arguments
+    o->max_peer_macs = max_peer_macs;
+    o->max_peer_groups = max_peer_groups;
+    o->igmp_group_membership_interval = igmp_group_membership_interval;
+    o->igmp_last_member_query_time = igmp_last_member_query_time;
+    o->reactor = reactor;
+    
+    // init peers list
+    LinkedList1_Init(&o->peers_list);
+    
+    // init MAC tree
+    FDMacsTree_Init(&o->macs_tree);
+    
+    // init multicast tree
+    FDMulticastTree_Init(&o->multicast_tree);
+    
+    // init decide state
+    o->decide_state = DECIDE_STATE_NONE;
+    
+    // set no current flood peer
+    o->decide_flood_current = NULL;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void FrameDecider_Free (FrameDecider *o)
+{
+    ASSERT(FDMulticastTree_IsEmpty(&o->multicast_tree))
+    ASSERT(FDMacsTree_IsEmpty(&o->macs_tree))
+    ASSERT(LinkedList1_IsEmpty(&o->peers_list))
+    DebugObject_Free(&o->d_obj);
+}
+
+void FrameDecider_AnalyzeAndDecide (FrameDecider *o, const uint8_t *frame, int frame_len)
+{
+    ASSERT(frame_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    // reset decide state
+    switch (o->decide_state) {
+        case DECIDE_STATE_NONE:
+            break;
+        case DECIDE_STATE_UNICAST:
+            break;
+        case DECIDE_STATE_FLOOD:
+            break;
+        case DECIDE_STATE_MULTICAST:
+            LinkedList3Iterator_Free(&o->decide_multicast_it);
+            return;
+        default:
+            ASSERT(0);
+    }
+    o->decide_state = DECIDE_STATE_NONE;
+    o->decide_flood_current = NULL;
+    
+    // analyze frame
+    
+    const uint8_t *pos = frame;
+    int len = frame_len;
+    
+    if (len < sizeof(struct ethernet_header)) {
+        return;
+    }
+    struct ethernet_header eh;
+    memcpy(&eh, pos, sizeof(eh));
+    pos += sizeof(struct ethernet_header);
+    len -= sizeof(struct ethernet_header);
+    
+    int is_igmp = 0;
+    
+    switch (ntoh16(eh.type)) {
+        case ETHERTYPE_IPV4: {
+            // check IPv4 header
+            struct ipv4_header ipv4_header;
+            if (!ipv4_check((uint8_t *)pos, len, &ipv4_header, (uint8_t **)&pos, &len)) {
+                BLog(BLOG_INFO, "decide: wrong IP packet");
+                goto out;
+            }
+            
+            // check if it's IGMP
+            if (ntoh8(ipv4_header.protocol) != IPV4_PROTOCOL_IGMP) {
+                goto out;
+            }
+            
+            // remember that it's IGMP; we have to flood IGMP frames
+            is_igmp = 1;
+            
+            // check IGMP header
+            if (len < sizeof(struct igmp_base)) {
+                BLog(BLOG_INFO, "decide: IGMP: short packet");
+                goto out;
+            }
+            struct igmp_base igmp_base;
+            memcpy(&igmp_base, pos, sizeof(igmp_base));
+            pos += sizeof(struct igmp_base);
+            len -= sizeof(struct igmp_base);
+            
+            switch (ntoh8(igmp_base.type)) {
+                case IGMP_TYPE_MEMBERSHIP_QUERY: {
+                    if (len == sizeof(struct igmp_v2_extra) && ntoh8(igmp_base.max_resp_code) != 0) {
+                        // V2 query
+                        struct igmp_v2_extra query;
+                        memcpy(&query, pos, sizeof(query));
+                        pos += sizeof(struct igmp_v2_extra);
+                        len -= sizeof(struct igmp_v2_extra);
+                        
+                        if (ntoh32(query.group) != 0) {
+                            // got a Group-Specific Query, lower group timers to LMQT
+                            lower_group_timers_to_lmqt(o, query.group);
+                        }
+                    }
+                    else if (len >= sizeof(struct igmp_v3_query_extra)) {
+                        // V3 query
+                        struct igmp_v3_query_extra query;
+                        memcpy(&query, pos, sizeof(query));
+                        pos += sizeof(struct igmp_v3_query_extra);
+                        len -= sizeof(struct igmp_v3_query_extra);
+                        
+                        // iterate sources
+                        uint16_t num_sources = ntoh16(query.number_of_sources);
+                        int i;
+                        for (i = 0; i < num_sources; i++) {
+                            // check source
+                            if (len < sizeof(struct igmp_source)) {
+                                BLog(BLOG_NOTICE, "decide: IGMP: short source");
+                                goto out;
+                            }
+                            pos += sizeof(struct igmp_source);
+                            len -= sizeof(struct igmp_source);
+                        }
+                        
+                        if (ntoh32(query.group) != 0 && num_sources == 0) {
+                            // got a Group-Specific Query, lower group timers to LMQT
+                            lower_group_timers_to_lmqt(o, query.group);
+                        }
+                    }
+                } break;
+            }
+        } break;
+    }
+    
+out:;
+    
+    const uint8_t broadcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+    const uint8_t multicast_mac_header[] = {0x01, 0x00, 0x5e};
+    
+    // if it's broadcast or IGMP, flood it
+    if (is_igmp || !memcmp(eh.dest, broadcast_mac, sizeof(broadcast_mac))) {
+        o->decide_state = DECIDE_STATE_FLOOD;
+        o->decide_flood_current = LinkedList1_GetFirst(&o->peers_list);
+        return;
+    }
+    
+    // if it's multicast, forward to all peers with the given sig
+    if (!memcmp(eh.dest, multicast_mac_header, sizeof(multicast_mac_header))) {
+        // extract group's sig from destination MAC
+        uint32_t sig = compute_sig_for_mac(eh.dest);
+        
+        // look up the sig in multicast tree
+        struct _FrameDecider_group_entry *master = FDMulticastTree_LookupExact(&o->multicast_tree, 0, sig);
+        if (master) {
+            ASSERT(master->is_master)
+            
+            o->decide_state = DECIDE_STATE_MULTICAST;
+            LinkedList3Iterator_Init(&o->decide_multicast_it, LinkedList3Node_First(&master->sig_list_node), 1);
+        }
+        
+        return;
+    }
+    
+    // look for MAC entry
+    struct _FrameDecider_mac_entry *entry = FDMacsTree_LookupExact(&o->macs_tree, 0, eh.dest);
+    if (entry) {
+        o->decide_state = DECIDE_STATE_UNICAST;
+        o->decide_unicast_peer = entry->peer;
+        return;
+    }
+    
+    // unknown destination MAC, flood
+    o->decide_state = DECIDE_STATE_FLOOD;
+    o->decide_flood_current = LinkedList1_GetFirst(&o->peers_list);
+    return;
+}
+
+FrameDeciderPeer * FrameDecider_NextDestination (FrameDecider *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    switch (o->decide_state) {
+        case DECIDE_STATE_NONE: {
+            return NULL;
+        } break;
+            
+        case DECIDE_STATE_UNICAST: {
+            o->decide_state = DECIDE_STATE_NONE;
+            
+            return o->decide_unicast_peer;
+        } break;
+        
+        case DECIDE_STATE_FLOOD: {
+            if (!o->decide_flood_current) {
+                o->decide_state = DECIDE_STATE_NONE;
+                return NULL;
+            }
+            
+            LinkedList1Node *list_node = o->decide_flood_current;
+            o->decide_flood_current = LinkedList1Node_Next(o->decide_flood_current);
+            
+            FrameDeciderPeer *peer = UPPER_OBJECT(list_node, FrameDeciderPeer, list_node);
+            
+            return peer;
+        } break;
+        
+        case DECIDE_STATE_MULTICAST: {
+            LinkedList3Node *list_node = LinkedList3Iterator_Next(&o->decide_multicast_it);
+            if (!list_node) {
+                o->decide_state = DECIDE_STATE_NONE;
+                return NULL;
+            }
+            struct _FrameDecider_group_entry *group_entry = UPPER_OBJECT(list_node, struct _FrameDecider_group_entry, sig_list_node);
+            
+            return group_entry->peer;
+        } break;
+        
+        default:
+            ASSERT(0);
+            return NULL;
+    }
+}
+
+int FrameDeciderPeer_Init (FrameDeciderPeer *o, FrameDecider *d, void *user, BLog_logfunc logfunc)
+{
+    // init arguments
+    o->d = d;
+    o->user = user;
+    o->logfunc = logfunc;
+    
+    // allocate MAC entries
+    if (!(o->mac_entries = (struct _FrameDecider_mac_entry *)BAllocArray(d->max_peer_macs, sizeof(struct _FrameDecider_mac_entry)))) {
+        PeerLog(o, BLOG_ERROR, "failed to allocate MAC entries");
+        goto fail0;
+    }
+    
+    // allocate group entries
+    if (!(o->group_entries = (struct _FrameDecider_group_entry *)BAllocArray(d->max_peer_groups, sizeof(struct _FrameDecider_group_entry)))) {
+        PeerLog(o, BLOG_ERROR, "failed to allocate group entries");
+        goto fail1;
+    }
+    
+    // insert to peers list
+    LinkedList1_Append(&d->peers_list, &o->list_node);
+    
+    // init MAC entry lists
+    LinkedList1_Init(&o->mac_entries_free);
+    LinkedList1_Init(&o->mac_entries_used);
+    
+    // initialize MAC entries
+    for (int i = 0; i < d->max_peer_macs; i++) {
+        struct _FrameDecider_mac_entry *entry = &o->mac_entries[i];
+        
+        // set peer
+        entry->peer = o;
+        
+        // insert to free list
+        LinkedList1_Append(&o->mac_entries_free, &entry->list_node);
+    }
+    
+    // init group entry lists
+    LinkedList1_Init(&o->group_entries_free);
+    LinkedList1_Init(&o->group_entries_used);
+    
+    // initialize group entries
+    for (int i = 0; i < d->max_peer_groups; i++) {
+        struct _FrameDecider_group_entry *entry = &o->group_entries[i];
+        
+        // set peer
+        entry->peer = o;
+        
+        // insert to free list
+        LinkedList1_Append(&o->group_entries_free, &entry->list_node);
+        
+        // init timer
+        BTimer_Init(&entry->timer, 0, (BTimer_handler)group_entry_timer_handler, entry);
+    }
+    
+    // initialize groups tree
+    FDGroupsTree_Init(&o->groups_tree);
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    BFree(o->mac_entries);
+fail0:
+    return 0;
+}
+
+void FrameDeciderPeer_Free (FrameDeciderPeer *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    FrameDecider *d = o->d;
+    
+    // remove decide unicast reference
+    if (d->decide_state == DECIDE_STATE_UNICAST && d->decide_unicast_peer == o) {
+        d->decide_state = DECIDE_STATE_NONE;
+    }
+    
+    LinkedList1Node *node;
+    
+    // free group entries
+    for (node = LinkedList1_GetFirst(&o->group_entries_used); node; node = LinkedList1Node_Next(node)) {
+        struct _FrameDecider_group_entry *entry = UPPER_OBJECT(node, struct _FrameDecider_group_entry, list_node);
+        
+        // remove from multicast
+        remove_from_multicast(d, entry);
+        
+        // stop timer
+        BReactor_RemoveTimer(d->reactor, &entry->timer);
+    }
+    
+    // remove used MAC entries from tree
+    for (node = LinkedList1_GetFirst(&o->mac_entries_used); node; node = LinkedList1Node_Next(node)) {
+        struct _FrameDecider_mac_entry *entry = UPPER_OBJECT(node, struct _FrameDecider_mac_entry, list_node);
+        
+        // remove from tree
+        FDMacsTree_Remove(&d->macs_tree, 0, entry);
+    }
+    
+    // remove from peers list
+    if (d->decide_flood_current == &o->list_node) {
+        d->decide_flood_current = LinkedList1Node_Next(d->decide_flood_current);
+    }
+    LinkedList1_Remove(&d->peers_list, &o->list_node);
+    
+    // free group entries
+    BFree(o->group_entries);
+    
+    // free MAC entries
+    BFree(o->mac_entries);
+}
+
+void FrameDeciderPeer_Analyze (FrameDeciderPeer *o, const uint8_t *frame, int frame_len)
+{
+    ASSERT(frame_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    const uint8_t *pos = frame;
+    int len = frame_len;
+    
+    if (len < sizeof(struct ethernet_header)) {
+        goto out;
+    }
+    struct ethernet_header eh;
+    memcpy(&eh, pos, sizeof(eh));
+    pos += sizeof(struct ethernet_header);
+    len -= sizeof(struct ethernet_header);
+    
+    // register source MAC address with this peer
+    add_mac_to_peer(o, eh.source);
+    
+    switch (ntoh16(eh.type)) {
+        case ETHERTYPE_IPV4: {
+            // check IPv4 header
+            struct ipv4_header ipv4_header;
+            if (!ipv4_check((uint8_t *)pos, len, &ipv4_header, (uint8_t **)&pos, &len)) {
+                PeerLog(o, BLOG_INFO, "analyze: wrong IP packet");
+                goto out;
+            }
+            
+            // check if it's IGMP
+            if (ntoh8(ipv4_header.protocol) != IPV4_PROTOCOL_IGMP) {
+                goto out;
+            }
+            
+            // check IGMP header
+            if (len < sizeof(struct igmp_base)) {
+                PeerLog(o, BLOG_INFO, "analyze: IGMP: short packet");
+                goto out;
+            }
+            struct igmp_base igmp_base;
+            memcpy(&igmp_base, pos, sizeof(igmp_base));
+            pos += sizeof(struct igmp_base);
+            len -= sizeof(struct igmp_base);
+            
+            switch (ntoh8(igmp_base.type)) {
+                case IGMP_TYPE_V2_MEMBERSHIP_REPORT: {
+                    // check extra
+                    if (len < sizeof(struct igmp_v2_extra)) {
+                        PeerLog(o, BLOG_INFO, "analyze: IGMP: short v2 report");
+                        goto out;
+                    }
+                    struct igmp_v2_extra report;
+                    memcpy(&report, pos, sizeof(report));
+                    pos += sizeof(struct igmp_v2_extra);
+                    len -= sizeof(struct igmp_v2_extra);
+                    
+                    // add to group
+                    add_group_to_peer(o, report.group);
+                } break;
+                
+                case IGMP_TYPE_V3_MEMBERSHIP_REPORT: {
+                    // check extra
+                    if (len < sizeof(struct igmp_v3_report_extra)) {
+                        PeerLog(o, BLOG_INFO, "analyze: IGMP: short v3 report");
+                        goto out;
+                    }
+                    struct igmp_v3_report_extra report;
+                    memcpy(&report, pos, sizeof(report));
+                    pos += sizeof(struct igmp_v3_report_extra);
+                    len -= sizeof(struct igmp_v3_report_extra);
+                    
+                    // iterate records
+                    uint16_t num_records = ntoh16(report.number_of_group_records);
+                    for (int i = 0; i < num_records; i++) {
+                        // check record
+                        if (len < sizeof(struct igmp_v3_report_record)) {
+                            PeerLog(o, BLOG_INFO, "analyze: IGMP: short record header");
+                            goto out;
+                        }
+                        struct igmp_v3_report_record record;
+                        memcpy(&record, pos, sizeof(record));
+                        pos += sizeof(struct igmp_v3_report_record);
+                        len -= sizeof(struct igmp_v3_report_record);
+                        
+                        // iterate sources
+                        uint16_t num_sources = ntoh16(record.number_of_sources);
+                        int j;
+                        for (j = 0; j < num_sources; j++) {
+                            // check source
+                            if (len < sizeof(struct igmp_source)) {
+                                PeerLog(o, BLOG_INFO, "analyze: IGMP: short source");
+                                goto out;
+                            }
+                            pos += sizeof(struct igmp_source);
+                            len -= sizeof(struct igmp_source);
+                        }
+                        
+                        // check aux data
+                        uint16_t aux_len = ntoh16(record.aux_data_len);
+                        if (len < aux_len) {
+                            PeerLog(o, BLOG_INFO, "analyze: IGMP: short record aux data");
+                            goto out;
+                        }
+                        pos += aux_len;
+                        len -= aux_len;
+                        
+                        switch (record.type) {
+                            case IGMP_RECORD_TYPE_MODE_IS_INCLUDE:
+                            case IGMP_RECORD_TYPE_CHANGE_TO_INCLUDE_MODE:
+                                if (num_sources != 0) {
+                                    add_group_to_peer(o, record.group);
+                                }
+                                break;
+                            case IGMP_RECORD_TYPE_MODE_IS_EXCLUDE:
+                            case IGMP_RECORD_TYPE_CHANGE_TO_EXCLUDE_MODE:
+                                add_group_to_peer(o, record.group);
+                                break;
+                        }
+                    }
+                } break;
+            }
+        } break;
+    }
+    
+out:;
+}
diff --git a/external/badvpn_dns/client/FrameDecider.h b/external/badvpn_dns/client/FrameDecider.h
new file mode 100644
index 0000000..f2a2937
--- /dev/null
+++ b/external/badvpn_dns/client/FrameDecider.h
@@ -0,0 +1,196 @@
+/**
+ * @file FrameDecider.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Mudule which decides to which peers frames from the device are to be
+ * forwarded.
+ */
+
+#ifndef BADVPN_CLIENT_FRAMEDECIDER_H
+#define BADVPN_CLIENT_FRAMEDECIDER_H
+
+#include <stdint.h>
+
+#include <structure/LinkedList1.h>
+#include <structure/LinkedList3.h>
+#include <structure/SAvl.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+
+struct _FrameDeciderPeer;
+struct _FrameDecider_mac_entry;
+struct _FrameDecider_group_entry;
+
+typedef const uint8_t *FDMacsTree_key;
+
+#include "FrameDecider_macs_tree.h"
+#include <structure/SAvl_decl.h>
+
+#include "FrameDecider_groups_tree.h"
+#include <structure/SAvl_decl.h>
+
+#include "FrameDecider_multicast_tree.h"
+#include <structure/SAvl_decl.h>
+
+struct _FrameDecider_mac_entry {
+    struct _FrameDeciderPeer *peer;
+    LinkedList1Node list_node; // node in FrameDeciderPeer.mac_entries_free or FrameDeciderPeer.mac_entries_used
+    // defined when used:
+    uint8_t mac[6];
+    FDMacsTreeNode tree_node; // node in FrameDecider.macs_tree, indexed by mac
+};
+
+struct _FrameDecider_group_entry {
+    struct _FrameDeciderPeer *peer;
+    LinkedList1Node list_node; // node in FrameDeciderPeer.group_entries_free or FrameDeciderPeer.group_entries_used
+    BTimer timer; // timer for removing the group entry, running when used
+    // defined when used:
+    // basic group data
+    uint32_t group; // group address
+    FDGroupsTreeNode tree_node; // node in FrameDeciderPeer.groups_tree, indexed by group
+    // all that folows is managed by add_to_multicast() and remove_from_multicast()
+    LinkedList3Node sig_list_node; // node in list of group entries with the same sig
+    btime_t timer_endtime;
+    int is_master;
+    // defined when used and we are master:
+    struct {
+        uint32_t sig; // last 23 bits of group address
+        FDMulticastTreeNode tree_node; // node in FrameDecider.multicast_tree, indexed by sig
+    } master;
+};
+
+/**
+ * Object that represents a local device.
+ */
+typedef struct {
+    int max_peer_macs;
+    int max_peer_groups;
+    btime_t igmp_group_membership_interval;
+    btime_t igmp_last_member_query_time;
+    BReactor *reactor;
+    LinkedList1 peers_list;
+    FDMacsTree macs_tree;
+    FDMulticastTree multicast_tree;
+    int decide_state;
+    LinkedList1Node *decide_flood_current;
+    struct _FrameDeciderPeer *decide_unicast_peer;
+    LinkedList3Iterator decide_multicast_it;
+    DebugObject d_obj;
+} FrameDecider;
+
+/**
+ * Object that represents a peer that a local device can send frames to.
+ */
+typedef struct _FrameDeciderPeer {
+    FrameDecider *d;
+    void *user;
+    BLog_logfunc logfunc;
+    struct _FrameDecider_mac_entry *mac_entries;
+    struct _FrameDecider_group_entry *group_entries;
+    LinkedList1Node list_node; // node in FrameDecider.peers_list
+    LinkedList1 mac_entries_free;
+    LinkedList1 mac_entries_used;
+    LinkedList1 group_entries_free;
+    LinkedList1 group_entries_used;
+    FDGroupsTree groups_tree;
+    DebugObject d_obj;
+} FrameDeciderPeer;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param max_peer_macs maximum number of MAC addresses a peer may posess. Must be >0.
+ * @param max_peer_groups maximum number of multicast groups a peer may belong to. Must be >0.
+ * @param igmp_group_membership_interval IGMP Group Membership Interval value. When a join
+ *        is detected for a peer in {@link FrameDeciderPeer_Analyze}, this is how long we wait
+ *        for another join before we remove the group from the peer. Note that the group may
+ *        be removed sooner if the peer fails to respond to a Group-Specific Query (see below).
+ * @param igmp_last_member_query_time IGMP Last Member Query Time value. When a Group-Specific
+ *        Query is detected in {@link FrameDecider_AnalyzeAndDecide}, this is how long we wait for a peer
+ *        belonging to the group to send a join before we remove the group from it.
+ */
+void FrameDecider_Init (FrameDecider *o, int max_peer_macs, int max_peer_groups, btime_t igmp_group_membership_interval, btime_t igmp_last_member_query_time, BReactor *reactor);
+
+/**
+ * Frees the object.
+ * There must be no {@link FrameDeciderPeer} objects using this decider.
+ * 
+ * @param o the object
+ */
+void FrameDecider_Free (FrameDecider *o);
+
+/**
+ * Analyzes a frame read from the local device and starts deciding which peers
+ * the frame should be forwarded to.
+ * 
+ * @param o the object
+ * @param frame frame data
+ * @param frame_len frame length. Must be >=0.
+ */
+void FrameDecider_AnalyzeAndDecide (FrameDecider *o, const uint8_t *frame, int frame_len);
+
+/**
+ * Returns the next peer that the frame submitted to {@link FrameDecider_AnalyzeAndDecide} should be
+ * forwarded to.
+ * 
+ * @param o the object
+ * @return peer to forward the frame to, or NULL if no more
+ */
+FrameDeciderPeer * FrameDecider_NextDestination (FrameDecider *o);
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param d decider this peer will belong to
+ * @param user argument to log function
+ * @param logfunc function which prepends the log prefix using {@link BLog_Append}
+ * @return 1 on success, 0 on failure
+ */
+int FrameDeciderPeer_Init (FrameDeciderPeer *o, FrameDecider *d, void *user, BLog_logfunc logfunc) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void FrameDeciderPeer_Free (FrameDeciderPeer *o);
+
+/**
+ * Analyzes a frame received from the peer.
+ * 
+ * @param o the object
+ * @param frame frame data
+ * @param frame_len frame length. Must be >=0.
+ */
+void FrameDeciderPeer_Analyze (FrameDeciderPeer *o, const uint8_t *frame, int frame_len);
+
+#endif
diff --git a/external/badvpn_dns/client/FrameDecider_groups_tree.h b/external/badvpn_dns/client/FrameDecider_groups_tree.h
new file mode 100644
index 0000000..b52a947
--- /dev/null
+++ b/external/badvpn_dns/client/FrameDecider_groups_tree.h
@@ -0,0 +1,9 @@
+#define SAVL_PARAM_NAME FDGroupsTree
+#define SAVL_PARAM_FEATURE_COUNTS 0
+#define SAVL_PARAM_FEATURE_NOKEYS 0
+#define SAVL_PARAM_TYPE_ENTRY struct _FrameDecider_group_entry
+#define SAVL_PARAM_TYPE_KEY uint32_t
+#define SAVL_PARAM_TYPE_ARG int
+#define SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) B_COMPARE((entry1)->group, (entry2)->group)
+#define SAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) B_COMPARE((key1), (entry2)->group)
+#define SAVL_PARAM_MEMBER_NODE tree_node
diff --git a/external/badvpn_dns/client/FrameDecider_macs_tree.h b/external/badvpn_dns/client/FrameDecider_macs_tree.h
new file mode 100644
index 0000000..2145918
--- /dev/null
+++ b/external/badvpn_dns/client/FrameDecider_macs_tree.h
@@ -0,0 +1,9 @@
+#define SAVL_PARAM_NAME FDMacsTree
+#define SAVL_PARAM_FEATURE_COUNTS 0
+#define SAVL_PARAM_FEATURE_NOKEYS 0
+#define SAVL_PARAM_TYPE_ENTRY struct _FrameDecider_mac_entry
+#define SAVL_PARAM_TYPE_KEY FDMacsTree_key
+#define SAVL_PARAM_TYPE_ARG int
+#define SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) compare_macs((entry1)->mac, (entry2)->mac)
+#define SAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) compare_macs((key1), (entry2)->mac)
+#define SAVL_PARAM_MEMBER_NODE tree_node
diff --git a/external/badvpn_dns/client/FrameDecider_multicast_tree.h b/external/badvpn_dns/client/FrameDecider_multicast_tree.h
new file mode 100644
index 0000000..2731684
--- /dev/null
+++ b/external/badvpn_dns/client/FrameDecider_multicast_tree.h
@@ -0,0 +1,9 @@
+#define SAVL_PARAM_NAME FDMulticastTree
+#define SAVL_PARAM_FEATURE_COUNTS 0
+#define SAVL_PARAM_FEATURE_NOKEYS 0
+#define SAVL_PARAM_TYPE_ENTRY struct _FrameDecider_group_entry
+#define SAVL_PARAM_TYPE_KEY uint32_t
+#define SAVL_PARAM_TYPE_ARG int
+#define SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) B_COMPARE((entry1)->master.sig, (entry2)->master.sig)
+#define SAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) B_COMPARE((key1), (entry2)->master.sig)
+#define SAVL_PARAM_MEMBER_NODE master.tree_node
diff --git a/external/badvpn_dns/client/PasswordListener.c b/external/badvpn_dns/client/PasswordListener.c
new file mode 100644
index 0000000..5ec573b
--- /dev/null
+++ b/external/badvpn_dns/client/PasswordListener.c
@@ -0,0 +1,374 @@
+/**
+ * @file PasswordListener.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include <prerror.h>
+
+#include <ssl.h>
+
+#include <misc/offset.h>
+#include <misc/byteorder.h>
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <base/BLog.h>
+#include <security/BRandom.h>
+#include <nspr_support/DummyPRFileDesc.h>
+
+#include <client/PasswordListener.h>
+
+#include <generated/blog_channel_PasswordListener.h>
+
+static int password_comparator (void *user, uint64_t *p1, uint64_t *p2);
+static void remove_client (struct PasswordListenerClient *client);
+static void listener_handler (PasswordListener *l);
+static void client_connection_handler (struct PasswordListenerClient *client, int event);
+static void client_sslcon_handler (struct PasswordListenerClient *client, int event);
+static void client_receiver_handler (struct PasswordListenerClient *client);
+
+int password_comparator (void *user, uint64_t *p1, uint64_t *p2)
+{
+    return B_COMPARE(*p1, *p2);
+}
+
+void remove_client (struct PasswordListenerClient *client)
+{
+    PasswordListener *l = client->l;
+    
+    // stop using any buffers before they get freed
+    if (l->ssl) {
+        BSSLConnection_ReleaseBuffers(&client->sslcon);
+    }
+    
+    // free receiver
+    SingleStreamReceiver_Free(&client->receiver);
+    
+    // free SSL
+    if (l->ssl) {
+        BSSLConnection_Free(&client->sslcon);
+        ASSERT_FORCE(PR_Close(client->sock->ssl_prfd) == PR_SUCCESS)
+    }
+    
+    // free connection interfaces
+    BConnection_RecvAsync_Free(&client->sock->con);
+    BConnection_SendAsync_Free(&client->sock->con);
+    
+    // free connection
+    BConnection_Free(&client->sock->con);
+    
+    // free sslsocket structure
+    free(client->sock);
+    
+    // move to free list
+    LinkedList1_Remove(&l->clients_used, &client->list_node);
+    LinkedList1_Append(&l->clients_free, &client->list_node);
+}
+
+void listener_handler (PasswordListener *l)
+{
+    DebugObject_Access(&l->d_obj);
+    
+    // obtain client entry
+    if (LinkedList1_IsEmpty(&l->clients_free)) {
+        struct PasswordListenerClient *client = UPPER_OBJECT(LinkedList1_GetFirst(&l->clients_used), struct PasswordListenerClient, list_node);
+        remove_client(client);
+    }
+    struct PasswordListenerClient *client = UPPER_OBJECT(LinkedList1_GetLast(&l->clients_free), struct PasswordListenerClient, list_node);
+    LinkedList1_Remove(&l->clients_free, &client->list_node);
+    LinkedList1_Append(&l->clients_used, &client->list_node);
+    
+    // allocate sslsocket structure
+    if (!(client->sock = (sslsocket *)malloc(sizeof(*client->sock)))) {
+        BLog(BLOG_ERROR, "malloc failedt");
+        goto fail0;
+    }
+    
+    // accept connection
+    if (!BConnection_Init(&client->sock->con, BConnection_source_listener(&l->listener, NULL), l->bsys, client, (BConnection_handler)client_connection_handler)) {
+        BLog(BLOG_ERROR, "BConnection_Init failed");
+        goto fail1;
+    }
+    
+    BLog(BLOG_INFO, "Connection accepted");
+    
+    // init connection interfaces
+    BConnection_SendAsync_Init(&client->sock->con);
+    BConnection_RecvAsync_Init(&client->sock->con);
+    
+    StreamPassInterface *send_if = BConnection_SendAsync_GetIf(&client->sock->con);
+    StreamRecvInterface *recv_if = BConnection_RecvAsync_GetIf(&client->sock->con);
+    
+    if (l->ssl) {
+        // create bottom NSPR file descriptor
+        if (!BSSLConnection_MakeBackend(&client->sock->bottom_prfd, send_if, recv_if, l->twd, l->ssl_flags)) {
+            BLog(BLOG_ERROR, "BSSLConnection_MakeBackend failed");
+            goto fail2;
+        }
+        
+        // create SSL file descriptor from the bottom NSPR file descriptor
+        if (!(client->sock->ssl_prfd = SSL_ImportFD(l->model_prfd, &client->sock->bottom_prfd))) {
+            ASSERT_FORCE(PR_Close(&client->sock->bottom_prfd) == PR_SUCCESS)
+            goto fail2;
+        }
+        
+        // set server mode
+        if (SSL_ResetHandshake(client->sock->ssl_prfd, PR_TRUE) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_ResetHandshake failed");
+            goto fail3;
+        }
+        
+        // set require client certificate
+        if (SSL_OptionSet(client->sock->ssl_prfd, SSL_REQUEST_CERTIFICATE, PR_TRUE) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_OptionSet(SSL_REQUEST_CERTIFICATE) failed");
+            goto fail3;
+        }
+        if (SSL_OptionSet(client->sock->ssl_prfd, SSL_REQUIRE_CERTIFICATE, PR_TRUE) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_OptionSet(SSL_REQUIRE_CERTIFICATE) failed");
+            goto fail3;
+        }
+        
+        // initialize SSLConnection
+        BSSLConnection_Init(&client->sslcon, client->sock->ssl_prfd, 0, BReactor_PendingGroup(l->bsys), client, (BSSLConnection_handler)client_sslcon_handler);
+        
+        send_if = BSSLConnection_GetSendIf(&client->sslcon);
+        recv_if = BSSLConnection_GetRecvIf(&client->sslcon);
+    }
+    
+    // init receiver
+    SingleStreamReceiver_Init(&client->receiver, (uint8_t *)&client->recv_buffer, sizeof(client->recv_buffer), recv_if, BReactor_PendingGroup(l->bsys), client, (SingleStreamReceiver_handler)client_receiver_handler);
+    
+    return;
+    
+    // cleanup on error
+fail3:
+    if (l->ssl) {
+        ASSERT_FORCE(PR_Close(client->sock->ssl_prfd) == PR_SUCCESS)
+    }
+fail2:
+    BConnection_RecvAsync_Free(&client->sock->con);
+    BConnection_SendAsync_Free(&client->sock->con);
+    BConnection_Free(&client->sock->con);
+fail1:
+    free(client->sock);
+fail0:
+    LinkedList1_Remove(&l->clients_used, &client->list_node);
+    LinkedList1_Append(&l->clients_free, &client->list_node);
+}
+
+void client_connection_handler (struct PasswordListenerClient *client, int event)
+{
+    PasswordListener *l = client->l;
+    DebugObject_Access(&l->d_obj);
+    
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        BLog(BLOG_INFO, "connection closed");
+    } else {
+        BLog(BLOG_INFO, "connection error");
+    }
+    
+    remove_client(client);
+}
+
+void client_sslcon_handler (struct PasswordListenerClient *client, int event)
+{
+    PasswordListener *l = client->l;
+    DebugObject_Access(&l->d_obj);
+    ASSERT(l->ssl)
+    ASSERT(event == BSSLCONNECTION_EVENT_ERROR)
+    
+    BLog(BLOG_INFO, "SSL error");
+    
+    remove_client(client);
+}
+
+void client_receiver_handler (struct PasswordListenerClient *client)
+{
+    PasswordListener *l = client->l;
+    DebugObject_Access(&l->d_obj);
+    
+    // check password
+    uint64_t received_pass = ltoh64(client->recv_buffer);
+    BAVLNode *pw_tree_node = BAVL_LookupExact(&l->passwords, &received_pass);
+    if (!pw_tree_node) {
+        BLog(BLOG_WARNING, "unknown password");
+        remove_client(client);
+        return;
+    }
+    PasswordListener_pwentry *pw_entry = UPPER_OBJECT(pw_tree_node, PasswordListener_pwentry, tree_node);
+    
+    BLog(BLOG_INFO, "Password recognized");
+    
+    // remove password entry
+    BAVL_Remove(&l->passwords, &pw_entry->tree_node);
+    
+    // stop using any buffers before they get freed
+    if (l->ssl) {
+        BSSLConnection_ReleaseBuffers(&client->sslcon);
+    }
+    
+    // free receiver
+    SingleStreamReceiver_Free(&client->receiver);
+    
+    if (l->ssl) {
+        // free SSL connection
+        BSSLConnection_Free(&client->sslcon);
+    } else {
+        // free connection interfaces
+        BConnection_RecvAsync_Free(&client->sock->con);
+        BConnection_SendAsync_Free(&client->sock->con);
+    }
+    
+    // remove connection handler
+    BConnection_SetHandlers(&client->sock->con, NULL, NULL);
+    
+    // move client entry to free list
+    LinkedList1_Remove(&l->clients_used, &client->list_node);
+    LinkedList1_Append(&l->clients_free, &client->list_node);
+    
+    // give the socket to the handler
+    pw_entry->handler_client(pw_entry->user, client->sock);
+    return;
+}
+
+int PasswordListener_Init (PasswordListener *l, BReactor *bsys, BThreadWorkDispatcher *twd, BAddr listen_addr, int max_clients, int ssl, int ssl_flags, CERTCertificate *cert, SECKEYPrivateKey *key)
+{
+    ASSERT(BConnection_AddressSupported(listen_addr))
+    ASSERT(max_clients > 0)
+    ASSERT(ssl == 0 || ssl == 1)
+    
+    // init arguments
+    l->bsys = bsys;
+    l->twd = twd;
+    l->ssl = ssl;
+    l->ssl_flags = ssl_flags;
+    
+    // allocate client entries
+    if (!(l->clients_data = (struct PasswordListenerClient *)BAllocArray(max_clients, sizeof(struct PasswordListenerClient)))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        goto fail0;
+    }
+    
+    if (l->ssl) {
+        // initialize model SSL fd
+        DummyPRFileDesc_Create(&l->model_dprfd);
+        if (!(l->model_prfd = SSL_ImportFD(NULL, &l->model_dprfd))) {
+            BLog(BLOG_ERROR, "SSL_ImportFD failed");
+            ASSERT_FORCE(PR_Close(&l->model_dprfd) == PR_SUCCESS)
+            goto fail1;
+        }
+        
+        // set server certificate
+        if (SSL_ConfigSecureServer(l->model_prfd, cert, key, NSS_FindCertKEAType(cert)) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_ConfigSecureServer failed");
+            goto fail2;
+        }
+    }
+    
+    // initialize client entries
+    LinkedList1_Init(&l->clients_free);
+    LinkedList1_Init(&l->clients_used);
+    for (int i = 0; i < max_clients; i++) {
+        struct PasswordListenerClient *conn = &l->clients_data[i];
+        conn->l = l;
+        LinkedList1_Append(&l->clients_free, &conn->list_node);
+    }
+    
+    // initialize passwords tree
+    BAVL_Init(&l->passwords, OFFSET_DIFF(PasswordListener_pwentry, password, tree_node), (BAVL_comparator)password_comparator, NULL);
+    
+    // initialize listener
+    if (!BListener_Init(&l->listener, listen_addr,  l->bsys, l, (BListener_handler)listener_handler)) {
+        BLog(BLOG_ERROR, "Listener_Init failed");
+        goto fail2;
+    }
+    
+    DebugObject_Init(&l->d_obj);
+    return 1;
+    
+    // cleanup
+fail2:
+    if (l->ssl) {
+        ASSERT_FORCE(PR_Close(l->model_prfd) == PR_SUCCESS)
+    }
+fail1:
+    BFree(l->clients_data);
+fail0:
+    return 0;
+}
+
+void PasswordListener_Free (PasswordListener *l)
+{
+    DebugObject_Free(&l->d_obj);
+
+    // free clients
+    LinkedList1Node *node;
+    while (node = LinkedList1_GetFirst(&l->clients_used)) {
+        struct PasswordListenerClient *client = UPPER_OBJECT(node, struct PasswordListenerClient, list_node);
+        remove_client(client);
+    }
+    
+    // free listener
+    BListener_Free(&l->listener);
+    
+    // free model SSL file descriptor
+    if (l->ssl) {
+        ASSERT_FORCE(PR_Close(l->model_prfd) == PR_SUCCESS)
+    }
+    
+    // free client entries
+    BFree(l->clients_data);
+}
+
+uint64_t PasswordListener_AddEntry (PasswordListener *l, PasswordListener_pwentry *entry, PasswordListener_handler_client handler_client, void *user)
+{
+    DebugObject_Access(&l->d_obj);
+    
+    while (1) {
+        // generate password
+        BRandom_randomize((uint8_t *)&entry->password, sizeof(entry->password));
+        
+        // try inserting
+        if (BAVL_Insert(&l->passwords, &entry->tree_node, NULL)) {
+            break;
+        }
+    }
+    
+    entry->handler_client = handler_client;
+    entry->user = user;
+    
+    return entry->password;
+}
+
+void PasswordListener_RemoveEntry (PasswordListener *l, PasswordListener_pwentry *entry)
+{
+    DebugObject_Access(&l->d_obj);
+    
+    // remove
+    BAVL_Remove(&l->passwords, &entry->tree_node);
+}
diff --git a/external/badvpn_dns/client/PasswordListener.h b/external/badvpn_dns/client/PasswordListener.h
new file mode 100644
index 0000000..bbc0bd1
--- /dev/null
+++ b/external/badvpn_dns/client/PasswordListener.h
@@ -0,0 +1,156 @@
+/**
+ * @file PasswordListener.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object used to listen on a socket, accept clients and identify them
+ * based on a number they send.
+ */
+
+#ifndef BADVPN_CLIENT_PASSWORDLISTENER_H
+#define BADVPN_CLIENT_PASSWORDLISTENER_H
+
+#include <stdint.h>
+
+#include <prio.h>
+
+#include <cert.h>
+#include <keyhi.h>
+
+#include <misc/debug.h>
+#include <misc/sslsocket.h>
+#include <structure/LinkedList1.h>
+#include <structure/BAVL.h>
+#include <base/DebugObject.h>
+#include <flow/SingleStreamReceiver.h>
+#include <system/BConnection.h>
+#include <nspr_support/BSSLConnection.h>
+
+/**
+ * Handler function called when a client identifies itself with a password
+ * belonging to one of the password entries.
+ * The password entry is unregistered before the handler is called
+ * and must not be unregistered again.
+ * 
+ * @param user as in {@link PasswordListener_AddEntry}
+ * @param sock structure containing a {@link BConnection} and, if TLS is enabled,
+ *             the SSL socket with the bottom layer connected to the async interfaces
+ *             of the {@link BConnection} object. The structure was allocated with
+ *             malloc() and the user is responsible for freeing it.
+ */
+typedef void (*PasswordListener_handler_client) (void *user, sslsocket *sock);
+
+struct PasswordListenerClient;
+
+/**
+ * Object used to listen on a socket, accept clients and identify them
+ * based on a number they send.
+ */
+typedef struct {
+    BReactor *bsys;
+    BThreadWorkDispatcher *twd;
+    int ssl;
+    int ssl_flags;
+    PRFileDesc model_dprfd;
+    PRFileDesc *model_prfd;
+    struct PasswordListenerClient *clients_data;
+    LinkedList1 clients_free;
+    LinkedList1 clients_used;
+    BAVL passwords;
+    BListener listener;
+    DebugObject d_obj;
+} PasswordListener;
+
+typedef struct {
+    uint64_t password;
+    BAVLNode tree_node;
+    PasswordListener_handler_client handler_client;
+    void *user;
+} PasswordListener_pwentry;
+
+struct PasswordListenerClient {
+    PasswordListener *l;
+    LinkedList1Node list_node;
+    sslsocket *sock;
+    BSSLConnection sslcon;
+    SingleStreamReceiver receiver;
+    uint64_t recv_buffer;
+};
+
+/**
+ * Initializes the object.
+ * 
+ * @param l the object
+ * @param bsys reactor we live in
+ * @param twd thread work dispatcher. May be NULL if ssl_flags does not request performing SSL
+ *            operations in threads.
+ * @param listen_addr address to listen on. Must be supported according to {@link BConnection_AddressSupported}.
+ * @param max_clients maximum number of client to hold until they are identified.
+ *                    Must be >0.
+ * @param ssl whether to use TLS. Must be 1 or 0.
+ * @param ssl_flags flags passed down to {@link BSSLConnection_MakeBackend}. May be used to
+ *                  request performing SSL operations in threads.
+ * @param cert if using TLS, the server certificate
+ * @param key if using TLS, the private key
+ * @return 1 on success, 0 on failure
+ */
+int PasswordListener_Init (PasswordListener *l, BReactor *bsys, BThreadWorkDispatcher *twd, BAddr listen_addr, int max_clients, int ssl, int ssl_flags, CERTCertificate *cert, SECKEYPrivateKey *key) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ * 
+ * @param l the object
+ */
+void PasswordListener_Free (PasswordListener *l);
+
+/**
+ * Registers a password entry.
+ * 
+ * @param l the object
+ * @param entry uninitialized entry structure
+ * @param handler_client handler function to call when a client identifies
+ *                       with the password which this function returns
+ * @param user value to pass to handler function
+ * @return password which a client should send to be recognized and
+ *         dispatched to the handler function. Should be treated as a numeric
+ *         value, which a client should as a little-endian 64-bit unsigned integer
+ *         when it connects.
+ */
+uint64_t PasswordListener_AddEntry (PasswordListener *l, PasswordListener_pwentry *entry, PasswordListener_handler_client handler_client, void *user);
+
+/**
+ * Unregisters a password entry.
+ * Note that when a client is dispatched, its entry is unregistered
+ * automatically and must not be unregistered again here.
+ * 
+ * @param l the object
+ * @param entry entry to unregister
+ */
+void PasswordListener_RemoveEntry (PasswordListener *l, PasswordListener_pwentry *entry);
+
+#endif
diff --git a/external/badvpn_dns/client/PeerChat.c b/external/badvpn_dns/client/PeerChat.c
new file mode 100644
index 0000000..d9dd966
--- /dev/null
+++ b/external/badvpn_dns/client/PeerChat.c
@@ -0,0 +1,433 @@
+/**
+ * @file PeerChat.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include <ssl.h>
+#include <sslerr.h>
+
+#include <misc/byteorder.h>
+#include <security/BRandom.h>
+
+#include "PeerChat.h"
+
+#include <generated/blog_channel_PeerChat.h>
+
+#define PeerLog(_o, ...) BLog_LogViaFunc((_o)->logfunc, (_o)->user, BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void report_error (PeerChat *o)
+{
+    DebugError_AssertNoError(&o->d_err);
+    
+    DEBUGERROR(&o->d_err, o->handler_error(o->user))
+    return;
+}
+
+static void recv_job_handler (PeerChat *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv_data_len >= 0)
+    ASSERT(o->recv_data_len <= SC_MAX_MSGLEN)
+    
+    int data_len = o->recv_data_len;
+    
+    // set no received data
+    o->recv_data_len = -1;
+    
+#ifdef PEERCHAT_SIMULATE_ERROR
+    uint8_t x;
+    BRandom_randomize(&x, sizeof(x));
+    if (x < PEERCHAT_SIMULATE_ERROR) {
+        PeerLog(o, BLOG_ERROR, "simulate error");
+        report_error(o);
+        return;
+    }
+#endif
+    
+    if (o->ssl_mode != PEERCHAT_SSL_NONE) {
+        // buffer data
+        if (!SimpleStreamBuffer_Write(&o->ssl_recv_buf, o->recv_data, data_len)) {
+            PeerLog(o, BLOG_ERROR, "out of recv buffer");
+            report_error(o);
+            return;
+        }
+    } else {
+        // call message handler
+        o->handler_message(o->user, o->recv_data, data_len);
+        return;
+    }
+}
+
+static void ssl_con_handler (PeerChat *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->ssl_mode == PEERCHAT_SSL_CLIENT || o->ssl_mode == PEERCHAT_SSL_SERVER)
+    ASSERT(event == BSSLCONNECTION_EVENT_ERROR)
+    
+    PeerLog(o, BLOG_ERROR, "SSL error");
+    
+    report_error(o);
+    return;
+}
+
+static SECStatus client_auth_data_callback (PeerChat *o, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->ssl_mode == PEERCHAT_SSL_CLIENT)
+    
+    CERTCertificate *cert = CERT_DupCertificate(o->ssl_cert);
+    if (!cert) {
+        PeerLog(o, BLOG_ERROR, "CERT_DupCertificate failed");
+        goto fail0;
+    }
+    
+    SECKEYPrivateKey *key = SECKEY_CopyPrivateKey(o->ssl_key);
+    if (!key) {
+        PeerLog(o, BLOG_ERROR, "SECKEY_CopyPrivateKey failed");
+        goto fail1;
+    }
+    
+    *pRetCert = cert;
+    *pRetKey = key;
+    return SECSuccess;
+    
+fail1:
+    CERT_DestroyCertificate(cert);
+fail0:
+    return SECFailure;
+}
+
+static SECStatus auth_certificate_callback (PeerChat *o, PRFileDesc *fd, PRBool checkSig, PRBool isServer)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->ssl_mode == PEERCHAT_SSL_CLIENT || o->ssl_mode == PEERCHAT_SSL_SERVER)
+    
+    // This callback is used to bypass checking the server's domain name, as peers
+    // don't have domain names. We byte-compare the certificate to the one reported
+    // by the server anyway.
+    
+    SECStatus ret = SECFailure;
+    
+    CERTCertificate *cert = SSL_PeerCertificate(o->ssl_prfd);
+    if (!cert) {
+        PeerLog(o, BLOG_ERROR, "SSL_PeerCertificate failed");
+        PORT_SetError(SSL_ERROR_BAD_CERTIFICATE);
+        goto fail1;
+    }
+    
+    SECCertUsage cert_usage = (o->ssl_mode == PEERCHAT_SSL_CLIENT ? certUsageSSLServer : certUsageSSLClient);
+    
+    if (CERT_VerifyCertNow(CERT_GetDefaultCertDB(), cert, PR_TRUE, cert_usage, SSL_RevealPinArg(o->ssl_prfd)) != SECSuccess) {
+        goto fail2;
+    }
+    
+    // compare to certificate provided by the server
+    SECItem der = cert->derCert;
+    if (der.len != o->ssl_peer_cert_len || memcmp(der.data, o->ssl_peer_cert, der.len)) {
+        PeerLog(o, BLOG_ERROR, "peer certificate doesn't match");
+        PORT_SetError(SSL_ERROR_BAD_CERTIFICATE);
+        goto fail2;
+    }
+    
+    ret = SECSuccess;
+    
+fail2:
+    CERT_DestroyCertificate(cert);
+fail1:
+    return ret;
+}
+
+static void ssl_recv_if_handler_send (PeerChat *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->ssl_mode == PEERCHAT_SSL_CLIENT || o->ssl_mode == PEERCHAT_SSL_SERVER)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_MSGLEN)
+    
+    // accept packet
+    PacketPassInterface_Done(&o->ssl_recv_if);
+    
+    // call message handler
+    o->handler_message(o->user, data, data_len);
+    return;
+}
+
+static void ssl_recv_decoder_handler_error (PeerChat *o)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->ssl_mode == PEERCHAT_SSL_CLIENT || o->ssl_mode == PEERCHAT_SSL_SERVER)
+    
+    PeerLog(o, BLOG_ERROR, "decoder error");
+    
+    report_error(o);
+    return;
+}
+
+int PeerChat_Init (PeerChat *o, peerid_t peer_id, int ssl_mode, int ssl_flags, CERTCertificate *ssl_cert, SECKEYPrivateKey *ssl_key,
+                   uint8_t *ssl_peer_cert, int ssl_peer_cert_len, BPendingGroup *pg, BThreadWorkDispatcher *twd, void *user,
+                   BLog_logfunc logfunc,
+                   PeerChat_handler_error handler_error,
+                   PeerChat_handler_message handler_message)
+{
+    ASSERT(ssl_mode == PEERCHAT_SSL_NONE || ssl_mode == PEERCHAT_SSL_CLIENT || ssl_mode == PEERCHAT_SSL_SERVER)
+    ASSERT(ssl_mode == PEERCHAT_SSL_NONE || ssl_peer_cert_len >= 0)
+    ASSERT(logfunc)
+    ASSERT(handler_error)
+    ASSERT(handler_message)
+    
+    // init arguments
+    o->ssl_mode = ssl_mode;
+    o->ssl_cert = ssl_cert;
+    o->ssl_key = ssl_key;
+    o->ssl_peer_cert = ssl_peer_cert;
+    o->ssl_peer_cert_len = ssl_peer_cert_len;
+    o->user = user;
+    o->logfunc = logfunc;
+    o->handler_error = handler_error;
+    o->handler_message = handler_message;
+    
+    // init copier
+    PacketCopier_Init(&o->copier, SC_MAX_MSGLEN, pg);
+    
+    // init SC encoder
+    SCOutmsgEncoder_Init(&o->sc_encoder, peer_id, PacketCopier_GetOutput(&o->copier), pg);
+    
+    // init PacketProto encoder
+    PacketProtoEncoder_Init(&o->pp_encoder, SCOutmsgEncoder_GetOutput(&o->sc_encoder), pg);
+    
+    // init recv job
+    BPending_Init(&o->recv_job, pg, (BPending_handler)recv_job_handler, o);
+    
+    // set no received data
+    o->recv_data_len = -1;
+    
+    PacketPassInterface *send_buf_output = PacketCopier_GetInput(&o->copier);
+    
+    if (o->ssl_mode != PEERCHAT_SSL_NONE) {
+        // init receive buffer
+        if (!SimpleStreamBuffer_Init(&o->ssl_recv_buf, PEERCHAT_SSL_RECV_BUF_SIZE, pg)) {
+            PeerLog(o, BLOG_ERROR, "SimpleStreamBuffer_Init failed");
+            goto fail1;
+        }
+        
+        // init SSL StreamPacketSender
+        StreamPacketSender_Init(&o->ssl_sp_sender, send_buf_output, pg);
+        
+        // init SSL bottom prfd
+        if (!BSSLConnection_MakeBackend(&o->ssl_bottom_prfd, StreamPacketSender_GetInput(&o->ssl_sp_sender), SimpleStreamBuffer_GetOutput(&o->ssl_recv_buf), twd, ssl_flags)) {
+            PeerLog(o, BLOG_ERROR, "BSSLConnection_MakeBackend failed");
+            goto fail2;
+        }
+        
+        // init SSL prfd
+        if (!(o->ssl_prfd = SSL_ImportFD(NULL, &o->ssl_bottom_prfd))) {
+            ASSERT_FORCE(PR_Close(&o->ssl_bottom_prfd) == PR_SUCCESS)
+            PeerLog(o, BLOG_ERROR, "SSL_ImportFD failed");
+            goto fail2;
+        }
+        
+        // set client or server mode
+        if (SSL_ResetHandshake(o->ssl_prfd, (o->ssl_mode == PEERCHAT_SSL_SERVER ? PR_TRUE : PR_FALSE)) != SECSuccess) {
+            PeerLog(o, BLOG_ERROR, "SSL_ResetHandshake failed");
+            goto fail3;
+        }
+        
+        if (o->ssl_mode == PEERCHAT_SSL_SERVER) {
+            // set server certificate
+            if (SSL_ConfigSecureServer(o->ssl_prfd, o->ssl_cert, o->ssl_key, NSS_FindCertKEAType(o->ssl_cert)) != SECSuccess) {
+                PeerLog(o, BLOG_ERROR, "SSL_ConfigSecureServer failed");
+                goto fail3;
+            }
+            
+            // set require client certificate
+            if (SSL_OptionSet(o->ssl_prfd, SSL_REQUEST_CERTIFICATE, PR_TRUE) != SECSuccess) {
+                PeerLog(o, BLOG_ERROR, "SSL_OptionSet(SSL_REQUEST_CERTIFICATE) failed");
+                goto fail3;
+            }
+            if (SSL_OptionSet(o->ssl_prfd, SSL_REQUIRE_CERTIFICATE, PR_TRUE) != SECSuccess) {
+                PeerLog(o, BLOG_ERROR, "SSL_OptionSet(SSL_REQUIRE_CERTIFICATE) failed");
+                goto fail3;
+            }
+        } else {
+            // set client certificate callback
+            if (SSL_GetClientAuthDataHook(o->ssl_prfd, (SSLGetClientAuthData)client_auth_data_callback, o) != SECSuccess) {
+                PeerLog(o, BLOG_ERROR, "SSL_GetClientAuthDataHook failed");
+                goto fail3;
+            }
+        }
+        
+        // set verify peer certificate hook
+        if (SSL_AuthCertificateHook(o->ssl_prfd, (SSLAuthCertificate)auth_certificate_callback, o) != SECSuccess) {
+            PeerLog(o, BLOG_ERROR, "SSL_AuthCertificateHook failed");
+            goto fail3;
+        }
+        
+        // init SSL connection
+        BSSLConnection_Init(&o->ssl_con, o->ssl_prfd, 0, pg, o, (BSSLConnection_handler)ssl_con_handler);
+        
+        // init SSL PacketStreamSender
+        PacketStreamSender_Init(&o->ssl_ps_sender, BSSLConnection_GetSendIf(&o->ssl_con), sizeof(struct packetproto_header) + SC_MAX_MSGLEN, pg);
+        
+        // init SSL copier
+        PacketCopier_Init(&o->ssl_copier, SC_MAX_MSGLEN, pg);
+        
+        // init SSL encoder
+        PacketProtoEncoder_Init(&o->ssl_encoder, PacketCopier_GetOutput(&o->ssl_copier), pg);
+        
+        // init SSL buffer
+        if (!SinglePacketBuffer_Init(&o->ssl_buffer, PacketProtoEncoder_GetOutput(&o->ssl_encoder), PacketStreamSender_GetInput(&o->ssl_ps_sender), pg)) {
+            PeerLog(o, BLOG_ERROR, "SinglePacketBuffer_Init failed");
+            goto fail4;
+        }
+        
+        // init receive interface
+        PacketPassInterface_Init(&o->ssl_recv_if, SC_MAX_MSGLEN, (PacketPassInterface_handler_send)ssl_recv_if_handler_send, o, pg);
+        
+        // init receive decoder
+        if (!PacketProtoDecoder_Init(&o->ssl_recv_decoder, BSSLConnection_GetRecvIf(&o->ssl_con), &o->ssl_recv_if, pg, o, (PacketProtoDecoder_handler_error)ssl_recv_decoder_handler_error)) {
+            PeerLog(o, BLOG_ERROR, "PacketProtoDecoder_Init failed");
+            goto fail5;
+        }
+        
+        send_buf_output = PacketCopier_GetInput(&o->ssl_copier);
+    }
+    
+    // init send writer
+    BufferWriter_Init(&o->send_writer, SC_MAX_MSGLEN, pg);
+    
+    // init send buffer
+    if (!PacketBuffer_Init(&o->send_buf, BufferWriter_GetOutput(&o->send_writer), send_buf_output, PEERCHAT_SEND_BUF_SIZE, pg)) {
+        PeerLog(o, BLOG_ERROR, "PacketBuffer_Init failed");
+        goto fail6;
+    }
+    
+    DebugError_Init(&o->d_err, pg);
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail6:
+    BufferWriter_Free(&o->send_writer);
+    if (o->ssl_mode != PEERCHAT_SSL_NONE) {
+        PacketProtoDecoder_Free(&o->ssl_recv_decoder);
+fail5:
+        PacketPassInterface_Free(&o->ssl_recv_if);
+        SinglePacketBuffer_Free(&o->ssl_buffer);
+fail4:
+        PacketProtoEncoder_Free(&o->ssl_encoder);
+        PacketCopier_Free(&o->ssl_copier);
+        PacketStreamSender_Free(&o->ssl_ps_sender);
+        BSSLConnection_Free(&o->ssl_con);
+fail3:
+        ASSERT_FORCE(PR_Close(o->ssl_prfd) == PR_SUCCESS)
+fail2:
+        StreamPacketSender_Free(&o->ssl_sp_sender);
+        SimpleStreamBuffer_Free(&o->ssl_recv_buf);
+    }
+fail1:
+    BPending_Free(&o->recv_job);
+    PacketProtoEncoder_Free(&o->pp_encoder);
+    SCOutmsgEncoder_Free(&o->sc_encoder);
+    PacketCopier_Free(&o->copier);
+    return 0;
+}
+
+void PeerChat_Free (PeerChat *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    // stop using any buffers before they get freed
+    if (o->ssl_mode != PEERCHAT_SSL_NONE) {
+        BSSLConnection_ReleaseBuffers(&o->ssl_con);
+    }
+    
+    PacketBuffer_Free(&o->send_buf);
+    BufferWriter_Free(&o->send_writer);
+    if (o->ssl_mode != PEERCHAT_SSL_NONE) {
+        PacketProtoDecoder_Free(&o->ssl_recv_decoder);
+        PacketPassInterface_Free(&o->ssl_recv_if);
+        SinglePacketBuffer_Free(&o->ssl_buffer);
+        PacketProtoEncoder_Free(&o->ssl_encoder);
+        PacketCopier_Free(&o->ssl_copier);
+        PacketStreamSender_Free(&o->ssl_ps_sender);
+        BSSLConnection_Free(&o->ssl_con);
+        ASSERT_FORCE(PR_Close(o->ssl_prfd) == PR_SUCCESS)
+        StreamPacketSender_Free(&o->ssl_sp_sender);
+        SimpleStreamBuffer_Free(&o->ssl_recv_buf);
+    }
+    BPending_Free(&o->recv_job);
+    PacketProtoEncoder_Free(&o->pp_encoder);
+    SCOutmsgEncoder_Free(&o->sc_encoder);
+    PacketCopier_Free(&o->copier);
+}
+
+PacketRecvInterface * PeerChat_GetSendOutput (PeerChat *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return PacketProtoEncoder_GetOutput(&o->pp_encoder);
+}
+
+void PeerChat_InputReceived (PeerChat *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(o->recv_data_len == -1)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_MSGLEN)
+    
+    // remember data
+    o->recv_data = data;
+    o->recv_data_len = data_len;
+    
+    // set received job
+    BPending_Set(&o->recv_job);
+}
+
+int PeerChat_StartMessage (PeerChat *o, uint8_t **data)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    
+    return BufferWriter_StartPacket(&o->send_writer, data);
+}
+
+void PeerChat_EndMessage (PeerChat *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    DebugError_AssertNoError(&o->d_err);
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_MSGLEN)
+    
+    BufferWriter_EndPacket(&o->send_writer, data_len);
+}
diff --git a/external/badvpn_dns/client/PeerChat.h b/external/badvpn_dns/client/PeerChat.h
new file mode 100644
index 0000000..674e374
--- /dev/null
+++ b/external/badvpn_dns/client/PeerChat.h
@@ -0,0 +1,123 @@
+/**
+ * @file PeerChat.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_PEERCHAT_H
+#define BADVPN_PEERCHAT_H
+
+#include <cert.h>
+#include <keyhi.h>
+
+#include <protocol/packetproto.h>
+#include <protocol/scproto.h>
+#include <misc/debug.h>
+#include <misc/debugerror.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+#include <base/BLog.h>
+#include <flow/SinglePacketSender.h>
+#include <flow/PacketProtoEncoder.h>
+#include <flow/PacketCopier.h>
+#include <flow/StreamPacketSender.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/PacketBuffer.h>
+#include <flow/BufferWriter.h>
+#include <nspr_support/BSSLConnection.h>
+#include <client/SCOutmsgEncoder.h>
+#include <client/SimpleStreamBuffer.h>
+
+#define PEERCHAT_SSL_NONE 0
+#define PEERCHAT_SSL_CLIENT 1
+#define PEERCHAT_SSL_SERVER 2
+
+#define PEERCHAT_SSL_RECV_BUF_SIZE 4096
+#define PEERCHAT_SEND_BUF_SIZE 200
+
+//#define PEERCHAT_SIMULATE_ERROR 40
+
+typedef void (*PeerChat_handler_error) (void *user);
+typedef void (*PeerChat_handler_message) (void *user, uint8_t *data, int data_len);
+
+typedef struct {
+    int ssl_mode;
+    CERTCertificate *ssl_cert;
+    SECKEYPrivateKey *ssl_key;
+    uint8_t *ssl_peer_cert;
+    int ssl_peer_cert_len;
+    void *user;
+    BLog_logfunc logfunc;
+    PeerChat_handler_error handler_error;
+    PeerChat_handler_message handler_message;
+    
+    // transport
+    PacketProtoEncoder pp_encoder;
+    SCOutmsgEncoder sc_encoder;
+    PacketCopier copier;
+    BPending recv_job;
+    uint8_t *recv_data;
+    int recv_data_len;
+    
+    // SSL transport
+    StreamPacketSender ssl_sp_sender;
+    SimpleStreamBuffer ssl_recv_buf;
+    
+    // SSL connection
+    PRFileDesc ssl_bottom_prfd;
+    PRFileDesc *ssl_prfd;
+    BSSLConnection ssl_con;
+    
+    // SSL higher layer
+    PacketStreamSender ssl_ps_sender;
+    SinglePacketBuffer ssl_buffer;
+    PacketProtoEncoder ssl_encoder;
+    PacketCopier ssl_copier;
+    PacketProtoDecoder ssl_recv_decoder;
+    PacketPassInterface ssl_recv_if;
+    
+    // higher layer send buffer
+    PacketBuffer send_buf;
+    BufferWriter send_writer;
+    
+    DebugError d_err;
+    DebugObject d_obj;
+} PeerChat;
+
+int PeerChat_Init (PeerChat *o, peerid_t peer_id, int ssl_mode, int ssl_flags, CERTCertificate *ssl_cert, SECKEYPrivateKey *ssl_key,
+                   uint8_t *ssl_peer_cert, int ssl_peer_cert_len, BPendingGroup *pg, BThreadWorkDispatcher *twd, void *user,
+                   BLog_logfunc logfunc,
+                   PeerChat_handler_error handler_error,
+                   PeerChat_handler_message handler_message) WARN_UNUSED;
+void PeerChat_Free (PeerChat *o);
+PacketRecvInterface * PeerChat_GetSendOutput (PeerChat *o);
+void PeerChat_InputReceived (PeerChat *o, uint8_t *data, int data_len);
+int PeerChat_StartMessage (PeerChat *o, uint8_t **data) WARN_UNUSED;
+void PeerChat_EndMessage (PeerChat *o, int data_len);
+
+#endif
diff --git a/external/badvpn_dns/client/SCOutmsgEncoder.c b/external/badvpn_dns/client/SCOutmsgEncoder.c
new file mode 100644
index 0000000..83e8b27
--- /dev/null
+++ b/external/badvpn_dns/client/SCOutmsgEncoder.c
@@ -0,0 +1,104 @@
+/**
+ * @file SCOutmsgEncoder.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <limits.h>
+#include <string.h>
+
+#include <misc/balign.h>
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+
+#include "SCOutmsgEncoder.h"
+
+static void output_handler_recv (SCOutmsgEncoder *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(!o->output_packet)
+    ASSERT(data)
+    
+    // schedule receive
+    o->output_packet = data;
+    PacketRecvInterface_Receiver_Recv(o->input, o->output_packet + SCOUTMSG_OVERHEAD);
+}
+
+static void input_handler_done (SCOutmsgEncoder *o, int in_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->output_packet)
+    
+    // write SC header
+    struct sc_header header;
+    header.type = htol8(SCID_OUTMSG);
+    memcpy(o->output_packet, &header, sizeof(header));
+    
+    // write outmsg
+    struct sc_client_outmsg outmsg;
+    outmsg.clientid = htol16(o->peer_id);
+    memcpy(o->output_packet + sizeof(header), &outmsg, sizeof(outmsg));
+    
+    // finish output packet
+    o->output_packet = NULL;
+    PacketRecvInterface_Done(&o->output, SCOUTMSG_OVERHEAD + in_len);
+}
+
+void SCOutmsgEncoder_Init (SCOutmsgEncoder *o, peerid_t peer_id, PacketRecvInterface *input, BPendingGroup *pg)
+{
+    ASSERT(PacketRecvInterface_GetMTU(input) <= INT_MAX - SCOUTMSG_OVERHEAD)
+    
+    // init arguments
+    o->peer_id = peer_id;
+    o->input = input;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o);
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, SCOUTMSG_OVERHEAD + PacketRecvInterface_GetMTU(o->input), (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    // set no output packet
+    o->output_packet = NULL;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void SCOutmsgEncoder_Free (SCOutmsgEncoder *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free input
+    PacketRecvInterface_Free(&o->output);
+}
+
+PacketRecvInterface * SCOutmsgEncoder_GetOutput (SCOutmsgEncoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
diff --git a/external/badvpn_dns/client/SCOutmsgEncoder.h b/external/badvpn_dns/client/SCOutmsgEncoder.h
new file mode 100644
index 0000000..05d4cb2
--- /dev/null
+++ b/external/badvpn_dns/client/SCOutmsgEncoder.h
@@ -0,0 +1,76 @@
+/**
+ * @file SCOutmsgEncoder.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_SCOUTMSGENCODER_H
+#define BADVPN_SCOUTMSGENCODER_H
+
+#include <protocol/scproto.h>
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+#define SCOUTMSG_OVERHEAD (sizeof(struct sc_header) + sizeof(struct sc_client_outmsg))
+
+/**
+ * A {@link PacketRecvInterface} layer which encodes SCProto outgoing messages.
+ */
+typedef struct {
+    peerid_t peer_id;
+    PacketRecvInterface *input;
+    PacketRecvInterface output;
+    uint8_t *output_packet;
+    DebugObject d_obj;
+} SCOutmsgEncoder;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param peer_id destination peer for messages
+ * @param input input interface. Its MTU muse be <= (INT_MAX - SCOUTMSG_OVERHEAD).
+ * @param pg pending group we live in
+ */
+void SCOutmsgEncoder_Init (SCOutmsgEncoder *o, peerid_t peer_id, PacketRecvInterface *input, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void SCOutmsgEncoder_Free (SCOutmsgEncoder *o);
+
+/**
+ * Returns the output interface.
+ * The MTU of the interface will be (SCOUTMSG_OVERHEAD + input MTU).
+ * 
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * SCOutmsgEncoder_GetOutput (SCOutmsgEncoder *o);
+
+#endif
diff --git a/external/badvpn_dns/client/SPProtoDecoder.c b/external/badvpn_dns/client/SPProtoDecoder.c
new file mode 100644
index 0000000..0855162
--- /dev/null
+++ b/external/badvpn_dns/client/SPProtoDecoder.c
@@ -0,0 +1,398 @@
+/**
+ * @file SPProtoDecoder.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include <misc/balign.h>
+#include <misc/byteorder.h>
+#include <security/BHash.h>
+
+#include "SPProtoDecoder.h"
+
+#include <generated/blog_channel_SPProtoDecoder.h>
+
+#define PeerLog(_o, ...) BLog_LogViaFunc((_o)->logfunc, (_o)->user, BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void decode_work_func (SPProtoDecoder *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(o->in_len <= o->input_mtu)
+    
+    uint8_t *in = o->in;
+    int in_len = o->in_len;
+    
+    o->tw_out_len = -1;
+    
+    uint8_t *plaintext;
+    int plaintext_len;
+    
+    // decrypt if needed
+    if (!SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        plaintext = in;
+        plaintext_len = in_len;
+    } else {
+        // input must be a multiple of blocks size
+        if (in_len % o->enc_block_size != 0) {
+            PeerLog(o, BLOG_WARNING, "packet size not a multiple of block size");
+            return;
+        }
+        
+        // input must have an IV block
+        if (in_len < o->enc_block_size) {
+            PeerLog(o, BLOG_WARNING, "packet does not have an IV");
+            return;
+        }
+        
+        // check if we have encryption key
+        if (!o->have_encryption_key) {
+            PeerLog(o, BLOG_WARNING, "have no encryption key");
+            return;
+        }
+        
+        // copy IV as BEncryption_Decrypt changes the IV
+        uint8_t iv[BENCRYPTION_MAX_BLOCK_SIZE];
+        memcpy(iv, in, o->enc_block_size);
+        
+        // decrypt
+        uint8_t *ciphertext = in + o->enc_block_size;
+        int ciphertext_len = in_len - o->enc_block_size;
+        plaintext = o->buf;
+        BEncryption_Decrypt(&o->encryptor, ciphertext, plaintext, ciphertext_len, iv);
+        
+        // read padding
+        if (ciphertext_len < o->enc_block_size) {
+            PeerLog(o, BLOG_WARNING, "packet does not have a padding block");
+            return;
+        }
+        int i;
+        for (i = ciphertext_len - 1; i >= ciphertext_len - o->enc_block_size; i--) {
+            if (plaintext[i] == 1) {
+                break;
+            }
+            if (plaintext[i] != 0) {
+                PeerLog(o, BLOG_WARNING, "packet padding wrong (nonzero byte)");
+                return;
+            }
+        }
+        if (i < ciphertext_len - o->enc_block_size) {
+            PeerLog(o, BLOG_WARNING, "packet padding wrong (all zeroes)");
+            return;
+        }
+        plaintext_len = i;
+    }
+    
+    // check for header
+    if (plaintext_len < SPPROTO_HEADER_LEN(o->sp_params)) {
+        PeerLog(o, BLOG_WARNING, "packet has no header");
+        return;
+    }
+    uint8_t *header = plaintext;
+    
+    // check data length
+    if (plaintext_len - SPPROTO_HEADER_LEN(o->sp_params) > o->output_mtu) {
+        PeerLog(o, BLOG_WARNING, "packet too long");
+        return;
+    }
+    
+    // check OTP
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        // remember seed and OTP (can't check from here)
+        struct spproto_otpdata header_otpd;
+        memcpy(&header_otpd, header + SPPROTO_HEADER_OTPDATA_OFF(o->sp_params), sizeof(header_otpd));
+        o->tw_out_seed_id = ltoh16(header_otpd.seed_id);
+        o->tw_out_otp = header_otpd.otp;
+    }
+    
+    // check hash
+    if (SPPROTO_HAVE_HASH(o->sp_params)) {
+        uint8_t *header_hash = header + SPPROTO_HEADER_HASH_OFF(o->sp_params);
+        // read hash
+        uint8_t hash[BHASH_MAX_SIZE];
+        memcpy(hash, header_hash, o->hash_size);
+        // zero hash in packet
+        memset(header_hash, 0, o->hash_size);
+        // calculate hash
+        uint8_t hash_calc[BHASH_MAX_SIZE];
+        BHash_calculate(o->sp_params.hash_mode, plaintext, plaintext_len, hash_calc);
+        // set hash field to its original value
+        memcpy(header_hash, hash, o->hash_size);
+        // compare hashes
+        if (memcmp(hash, hash_calc, o->hash_size)) {
+            PeerLog(o, BLOG_WARNING, "packet has wrong hash");
+            return;
+        }
+    }
+    
+    // return packet
+    o->tw_out = plaintext + SPPROTO_HEADER_LEN(o->sp_params);
+    o->tw_out_len = plaintext_len - SPPROTO_HEADER_LEN(o->sp_params);
+}
+
+static void decode_work_handler (SPProtoDecoder *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(o->tw_have)
+    DebugObject_Access(&o->d_obj);
+    
+    // free work
+    BThreadWork_Free(&o->tw);
+    o->tw_have = 0;
+    
+    // check OTP
+    if (SPPROTO_HAVE_OTP(o->sp_params) && o->tw_out_len >= 0) {
+        if (!OTPChecker_CheckOTP(&o->otpchecker, o->tw_out_seed_id, o->tw_out_otp)) {
+            PeerLog(o, BLOG_WARNING, "packet has wrong OTP");
+            o->tw_out_len = -1;
+        }
+    }
+    
+    if (o->tw_out_len < 0) {
+        // cannot decode, finish input packet
+        PacketPassInterface_Done(&o->input);
+        o->in_len = -1;
+    } else {
+        // submit decoded packet to output
+        PacketPassInterface_Sender_Send(o->output, o->tw_out, o->tw_out_len);
+    }
+}
+
+static void input_handler_send (SPProtoDecoder *o, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->input_mtu)
+    ASSERT(o->in_len == -1)
+    ASSERT(!o->tw_have)
+    DebugObject_Access(&o->d_obj);
+    
+    // remember input
+    o->in = data;
+    o->in_len = data_len;
+    
+    // start decoding
+    BThreadWork_Init(&o->tw, o->twd, (BThreadWork_handler_done)decode_work_handler, o, (BThreadWork_work_func)decode_work_func, o);
+    o->tw_have = 1;
+}
+
+static void output_handler_done (SPProtoDecoder *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(!o->tw_have)
+    DebugObject_Access(&o->d_obj);
+    
+    // finish input packet
+    PacketPassInterface_Done(&o->input);
+    o->in_len = -1;
+}
+
+static void maybe_stop_work_and_ignore (SPProtoDecoder *o)
+{
+    ASSERT(!(o->tw_have) || o->in_len >= 0)
+    
+    if (o->tw_have) {
+        // free work
+        BThreadWork_Free(&o->tw);
+        o->tw_have = 0;
+        
+        // ignore packet, receive next one
+        PacketPassInterface_Done(&o->input);
+        o->in_len = -1;
+    }
+}
+
+int SPProtoDecoder_Init (SPProtoDecoder *o, PacketPassInterface *output, struct spproto_security_params sp_params, int num_otp_seeds, BPendingGroup *pg, BThreadWorkDispatcher *twd, void *user, BLog_logfunc logfunc)
+{
+    spproto_assert_security_params(sp_params);
+    ASSERT(spproto_carrier_mtu_for_payload_mtu(sp_params, PacketPassInterface_GetMTU(output)) >= 0)
+    ASSERT(!SPPROTO_HAVE_OTP(sp_params) || num_otp_seeds >= 2)
+    
+    // init arguments
+    o->output = output;
+    o->sp_params = sp_params;
+    o->twd = twd;
+    o->user = user;
+    o->logfunc = logfunc;
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // remember output MTU
+    o->output_mtu = PacketPassInterface_GetMTU(o->output);
+    
+    // calculate hash size
+    if (SPPROTO_HAVE_HASH(o->sp_params)) {
+        o->hash_size = BHash_size(o->sp_params.hash_mode);
+    }
+    
+    // calculate encryption block and key sizes
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        o->enc_block_size = BEncryption_cipher_block_size(o->sp_params.encryption_mode);
+        o->enc_key_size = BEncryption_cipher_key_size(o->sp_params.encryption_mode);
+    }
+    
+    // calculate input MTU
+    o->input_mtu = spproto_carrier_mtu_for_payload_mtu(o->sp_params, o->output_mtu);
+    
+    // allocate plaintext buffer
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        int buf_size = balign_up((SPPROTO_HEADER_LEN(o->sp_params) + o->output_mtu + 1), o->enc_block_size);
+        if (!(o->buf = (uint8_t *)malloc(buf_size))) {
+            goto fail0;
+        }
+    }
+    
+    // init input
+    PacketPassInterface_Init(&o->input, o->input_mtu, (PacketPassInterface_handler_send)input_handler_send, o, pg);
+    
+    // init OTP checker
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        if (!OTPChecker_Init(&o->otpchecker, o->sp_params.otp_num, o->sp_params.otp_mode, num_otp_seeds, o->twd)) {
+            goto fail1;
+        }
+    }
+    
+    // have no encryption key
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { 
+        o->have_encryption_key = 0;
+    }
+    
+    // have no input packet
+    o->in_len = -1;
+    
+    // have no work
+    o->tw_have = 0;
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    PacketPassInterface_Free(&o->input);
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        free(o->buf);
+    }
+fail0:
+    return 0;
+}
+
+void SPProtoDecoder_Free (SPProtoDecoder *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free work
+    if (o->tw_have) {
+        BThreadWork_Free(&o->tw);
+    }
+    
+    // free encryptor
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params) && o->have_encryption_key) {
+        BEncryption_Free(&o->encryptor);
+    }
+    
+    // free OTP checker
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        OTPChecker_Free(&o->otpchecker);
+    }
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+    
+    // free plaintext buffer
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        free(o->buf);
+    }
+}
+
+PacketPassInterface * SPProtoDecoder_GetInput (SPProtoDecoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
+
+void SPProtoDecoder_SetEncryptionKey (SPProtoDecoder *o, uint8_t *encryption_key)
+{
+    ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // stop existing work
+    maybe_stop_work_and_ignore(o);
+    
+    // free encryptor
+    if (o->have_encryption_key) {
+        BEncryption_Free(&o->encryptor);
+    }
+    
+    // init encryptor
+    BEncryption_Init(&o->encryptor, BENCRYPTION_MODE_DECRYPT, o->sp_params.encryption_mode, encryption_key);
+    
+    // have encryption key
+    o->have_encryption_key = 1;
+}
+
+void SPProtoDecoder_RemoveEncryptionKey (SPProtoDecoder *o)
+{
+    ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // stop existing work
+    maybe_stop_work_and_ignore(o);
+    
+    if (o->have_encryption_key) {
+        // free encryptor
+        BEncryption_Free(&o->encryptor);
+        
+        // have no encryption key
+        o->have_encryption_key = 0;
+    }
+}
+
+void SPProtoDecoder_AddOTPSeed (SPProtoDecoder *o, uint16_t seed_id, uint8_t *key, uint8_t *iv)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    OTPChecker_AddSeed(&o->otpchecker, seed_id, key, iv);
+}
+
+void SPProtoDecoder_RemoveOTPSeeds (SPProtoDecoder *o)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    OTPChecker_RemoveSeeds(&o->otpchecker);
+}
+
+void SPProtoDecoder_SetHandlers (SPProtoDecoder *o, SPProtoDecoder_otp_handler otp_handler, void *user)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        OTPChecker_SetHandlers(&o->otpchecker, otp_handler, user);
+    }
+}
diff --git a/external/badvpn_dns/client/SPProtoDecoder.h b/external/badvpn_dns/client/SPProtoDecoder.h
new file mode 100644
index 0000000..3b5de71
--- /dev/null
+++ b/external/badvpn_dns/client/SPProtoDecoder.h
@@ -0,0 +1,171 @@
+/**
+ * @file SPProtoDecoder.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object which decodes packets according to SPProto.
+ */
+
+#ifndef BADVPN_CLIENT_SPPROTODECODER_H
+#define BADVPN_CLIENT_SPPROTODECODER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <protocol/spproto.h>
+#include <security/BEncryption.h>
+#include <security/OTPChecker.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * Handler called when OTP generation for a new seed is finished.
+ * 
+ * @param user as in {@link SPProtoDecoder_Init}
+ */
+typedef void (*SPProtoDecoder_otp_handler) (void *user);
+
+/**
+ * Object which decodes packets according to SPProto.
+ * Input is with {@link PacketPassInterface}.
+ * Output is with {@link PacketPassInterface}.
+ */
+typedef struct {
+    PacketPassInterface *output;
+    struct spproto_security_params sp_params;
+    BThreadWorkDispatcher *twd;
+    void *user;
+    BLog_logfunc logfunc;
+    int output_mtu;
+    int hash_size;
+    int enc_block_size;
+    int enc_key_size;
+    int input_mtu;
+    uint8_t *buf;
+    PacketPassInterface input;
+    OTPChecker otpchecker;
+    int have_encryption_key;
+    BEncryption encryptor;
+    uint8_t *in;
+    int in_len;
+    int tw_have;
+    BThreadWork tw;
+    uint16_t tw_out_seed_id;
+    otp_t tw_out_otp;
+    uint8_t *tw_out;
+    int tw_out_len;
+    DebugObject d_obj;
+} SPProtoDecoder;
+
+/**
+ * Initializes the object.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if
+ * {@link BThreadWorkDispatcher_UsingThreads}(twd) = 1.
+ *
+ * @param o the object
+ * @param output output interface. Its MTU must not be too large, i.e. this must hold:
+ *               spproto_carrier_mtu_for_payload_mtu(sp_params, output MTU) >= 0
+ * @param sp_params SPProto parameters
+ * @param encryption_key if using encryption, the encryption key
+ * @param num_otp_seeds if using OTPs, how many OTP seeds to keep for checking
+ *                      receiving packets. Must be >=2 if using OTPs.
+ * @param pg pending group
+ * @param twd thread work dispatcher
+ * @param user argument to handlers
+ * @param logfunc function which prepends the log prefix using {@link BLog_Append}
+ * @return 1 on success, 0 on failure
+ */
+int SPProtoDecoder_Init (SPProtoDecoder *o, PacketPassInterface *output, struct spproto_security_params sp_params, int num_otp_seeds, BPendingGroup *pg, BThreadWorkDispatcher *twd, void *user, BLog_logfunc logfunc) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void SPProtoDecoder_Free (SPProtoDecoder *o);
+
+/**
+ * Returns the input interface.
+ * The MTU of the input interface will depend on the output MTU and security parameters,
+ * that is spproto_carrier_mtu_for_payload_mtu(sp_params, output MTU).
+ *
+ * @param o the object
+ * @return input interface
+ */
+PacketPassInterface * SPProtoDecoder_GetInput (SPProtoDecoder *o);
+
+/**
+ * Sets an encryption key for decrypting packets.
+ * Encryption must be enabled.
+ *
+ * @param o the object
+ * @param encryption_key key to use
+ */
+void SPProtoDecoder_SetEncryptionKey (SPProtoDecoder *o, uint8_t *encryption_key);
+
+/**
+ * Removes an encryption key if one is configured.
+ * Encryption must be enabled.
+ *
+ * @param o the object
+ */
+void SPProtoDecoder_RemoveEncryptionKey (SPProtoDecoder *o);
+
+/**
+ * Starts generating OTPs for a seed to check received packets against.
+ * OTPs for this seed will not be recognized until the {@link SPProtoDecoder_otp_handler} handler
+ * is called.
+ * If OTPs are still being generated for the previous seed, it will be forgotten.
+ * OTPs must be enabled.
+ *
+ * @param o the object
+ * @param seed_id seed identifier
+ * @param key OTP encryption key
+ * @param iv OTP initialization vector
+ */
+void SPProtoDecoder_AddOTPSeed (SPProtoDecoder *o, uint16_t seed_id, uint8_t *key, uint8_t *iv);
+
+/**
+ * Removes all OTP seeds for checking received packets against.
+ * OTPs must be enabled.
+ *
+ * @param o the object
+ */
+void SPProtoDecoder_RemoveOTPSeeds (SPProtoDecoder *o);
+
+/**
+ * Sets handlers.
+ *
+ * @param o the object
+ * @param otp_handler handler called when OTP generation is finished
+ * @param user argument to handler
+ */
+void SPProtoDecoder_SetHandlers (SPProtoDecoder *o, SPProtoDecoder_otp_handler otp_handler, void *user);
+
+#endif
diff --git a/external/badvpn_dns/client/SPProtoEncoder.c b/external/badvpn_dns/client/SPProtoEncoder.c
new file mode 100644
index 0000000..fbbab50
--- /dev/null
+++ b/external/badvpn_dns/client/SPProtoEncoder.c
@@ -0,0 +1,436 @@
+/**
+ * @file SPProtoEncoder.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <misc/balign.h>
+#include <misc/offset.h>
+#include <misc/byteorder.h>
+#include <security/BRandom.h>
+#include <security/BHash.h>
+
+#include "SPProtoEncoder.h"
+
+static int can_encode (SPProtoEncoder *o);
+static void encode_packet (SPProtoEncoder *o);
+static void encode_work_func (SPProtoEncoder *o);
+static void encode_work_handler (SPProtoEncoder *o);
+static void maybe_encode (SPProtoEncoder *o);
+static void output_handler_recv (SPProtoEncoder *o, uint8_t *data);
+static void input_handler_done (SPProtoEncoder *o, int data_len);
+static void handler_job_hander (SPProtoEncoder *o);
+static void otpgenerator_handler (SPProtoEncoder *o);
+static void maybe_stop_work (SPProtoEncoder *o);
+
+static int can_encode (SPProtoEncoder *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(o->out_have)
+    ASSERT(!o->tw_have)
+    
+    return (
+        (!SPPROTO_HAVE_OTP(o->sp_params) || OTPGenerator_GetPosition(&o->otpgen) < o->sp_params.otp_num) &&
+        (!SPPROTO_HAVE_ENCRYPTION(o->sp_params) || o->have_encryption_key)
+    );
+}
+
+static void encode_packet (SPProtoEncoder *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(o->out_have)
+    ASSERT(!o->tw_have)
+    ASSERT(can_encode(o))
+    
+    // generate OTP, remember seed ID
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        o->tw_seed_id = o->otpgen_seed_id;
+        o->tw_otp = OTPGenerator_GetOTP(&o->otpgen);
+    }
+    
+    // start work
+    BThreadWork_Init(&o->tw, o->twd, (BThreadWork_handler_done)encode_work_handler, o, (BThreadWork_work_func)encode_work_func, o);
+    o->tw_have = 1;
+    
+    // schedule OTP warning handler
+    if (SPPROTO_HAVE_OTP(o->sp_params) && OTPGenerator_GetPosition(&o->otpgen) == o->otp_warning_count) {
+        BPending_Set(&o->handler_job);
+    }
+}
+
+static void encode_work_func (SPProtoEncoder *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(o->out_have)
+    ASSERT(!SPPROTO_HAVE_ENCRYPTION(o->sp_params) || o->have_encryption_key)
+    
+    ASSERT(o->in_len <= o->input_mtu)
+    
+    // determine plaintext location
+    uint8_t *plaintext = (SPPROTO_HAVE_ENCRYPTION(o->sp_params) ? o->buf : o->out);
+    
+    // plaintext begins with header
+    uint8_t *header = plaintext;
+    
+    // plaintext is header + payload
+    int plaintext_len = SPPROTO_HEADER_LEN(o->sp_params) + o->in_len;
+    
+    // write OTP
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        struct spproto_otpdata header_otpd;
+        header_otpd.seed_id = htol16(o->tw_seed_id);
+        header_otpd.otp = o->tw_otp;
+        memcpy(header + SPPROTO_HEADER_OTPDATA_OFF(o->sp_params), &header_otpd, sizeof(header_otpd));
+    }
+    
+    // write hash
+    if (SPPROTO_HAVE_HASH(o->sp_params)) {
+        uint8_t *header_hash = header + SPPROTO_HEADER_HASH_OFF(o->sp_params);
+        // zero hash field
+        memset(header_hash, 0, o->hash_size);
+        // calculate hash
+        uint8_t hash[BHASH_MAX_SIZE];
+        BHash_calculate(o->sp_params.hash_mode, plaintext, plaintext_len, hash);
+        // set hash field
+        memcpy(header_hash, hash, o->hash_size);
+    }
+    
+    int out_len;
+    
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        // encrypting pad(header + payload)
+        int cyphertext_len = balign_up((plaintext_len + 1), o->enc_block_size);
+        
+        // write padding
+        plaintext[plaintext_len] = 1;
+        for (int i = plaintext_len + 1; i < cyphertext_len; i++) {
+            plaintext[i] = 0;
+        }
+        
+        // generate IV
+        BRandom_randomize(o->out, o->enc_block_size);
+        
+        // copy IV because BEncryption_Encrypt changes the IV
+        uint8_t iv[BENCRYPTION_MAX_BLOCK_SIZE];
+        memcpy(iv, o->out, o->enc_block_size);
+        
+        // encrypt
+        BEncryption_Encrypt(&o->encryptor, plaintext, o->out + o->enc_block_size, cyphertext_len, iv);
+        out_len = o->enc_block_size + cyphertext_len;
+    } else {
+        out_len = plaintext_len;
+    }
+    
+    // remember length
+    o->tw_out_len = out_len;
+}
+
+static void encode_work_handler (SPProtoEncoder *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(o->out_have)
+    ASSERT(o->tw_have)
+    
+    // free work
+    BThreadWork_Free(&o->tw);
+    o->tw_have = 0;
+    
+    // finish packet
+    o->in_len = -1;
+    o->out_have = 0;
+    PacketRecvInterface_Done(&o->output, o->tw_out_len);
+}
+
+static void maybe_encode (SPProtoEncoder *o)
+{
+    if (o->in_len >= 0 && o->out_have && !o->tw_have && can_encode(o)) {
+        encode_packet(o);
+    }
+}
+
+static void output_handler_recv (SPProtoEncoder *o, uint8_t *data)
+{
+    ASSERT(o->in_len == -1)
+    ASSERT(!o->out_have)
+    ASSERT(!o->tw_have)
+    DebugObject_Access(&o->d_obj);
+    
+    // remember output packet
+    o->out_have = 1;
+    o->out = data;
+    
+    // determine plaintext location
+    uint8_t *plaintext = (SPPROTO_HAVE_ENCRYPTION(o->sp_params) ? o->buf : o->out);
+    
+    // schedule receive
+    PacketRecvInterface_Receiver_Recv(o->input, plaintext + SPPROTO_HEADER_LEN(o->sp_params));
+}
+
+static void input_handler_done (SPProtoEncoder *o, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->input_mtu)
+    ASSERT(o->in_len == -1)
+    ASSERT(o->out_have)
+    ASSERT(!o->tw_have)
+    DebugObject_Access(&o->d_obj);
+    
+    // remember input packet
+    o->in_len = data_len;
+    
+    // encode if possible
+    if (can_encode(o)) {
+        encode_packet(o);
+    }
+}
+
+static void handler_job_hander (SPProtoEncoder *o)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    if (o->handler) {
+        o->handler(o->user);
+        return;
+    }
+}
+
+static void otpgenerator_handler (SPProtoEncoder *o)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // remember seed ID
+    o->otpgen_seed_id = o->otpgen_pending_seed_id;
+    
+    // possibly continue I/O
+    maybe_encode(o);
+}
+
+static void maybe_stop_work (SPProtoEncoder *o)
+{
+    // stop existing work
+    if (o->tw_have) {
+        BThreadWork_Free(&o->tw);
+        o->tw_have = 0;
+    }
+}
+
+int SPProtoEncoder_Init (SPProtoEncoder *o, PacketRecvInterface *input, struct spproto_security_params sp_params, int otp_warning_count, BPendingGroup *pg, BThreadWorkDispatcher *twd)
+{
+    spproto_assert_security_params(sp_params);
+    ASSERT(spproto_carrier_mtu_for_payload_mtu(sp_params, PacketRecvInterface_GetMTU(input)) >= 0)
+    if (SPPROTO_HAVE_OTP(sp_params)) {
+        ASSERT(otp_warning_count > 0)
+        ASSERT(otp_warning_count <= sp_params.otp_num)
+    }
+    
+    // init arguments
+    o->input = input;
+    o->sp_params = sp_params;
+    o->otp_warning_count = otp_warning_count;
+    o->twd = twd;
+    
+    // set no handlers
+    o->handler = NULL;
+    
+    // calculate hash size
+    if (SPPROTO_HAVE_HASH(o->sp_params)) {
+        o->hash_size = BHash_size(o->sp_params.hash_mode);
+    }
+    
+    // calculate encryption block and key sizes
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        o->enc_block_size = BEncryption_cipher_block_size(o->sp_params.encryption_mode);
+        o->enc_key_size = BEncryption_cipher_key_size(o->sp_params.encryption_mode);
+    }
+    
+    // init otp generator
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        if (!OTPGenerator_Init(&o->otpgen, o->sp_params.otp_num, o->sp_params.otp_mode, o->twd, (OTPGenerator_handler)otpgenerator_handler, o)) {
+            goto fail0;
+        }
+    }
+    
+    // have no encryption key
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) { 
+        o->have_encryption_key = 0;
+    }
+    
+    // remember input MTU
+    o->input_mtu = PacketRecvInterface_GetMTU(o->input);
+    
+    // calculate output MTU
+    o->output_mtu = spproto_carrier_mtu_for_payload_mtu(o->sp_params, o->input_mtu);
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o);
+    
+    // have no input in buffer
+    o->in_len = -1;
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, o->output_mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    // have no output available
+    o->out_have = 0;
+    
+    // allocate plaintext buffer
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        int buf_size = balign_up((SPPROTO_HEADER_LEN(o->sp_params) + o->input_mtu + 1), o->enc_block_size);
+        if (!(o->buf = (uint8_t *)malloc(buf_size))) {
+            goto fail1;
+        }
+    }
+    
+    // init handler job
+    BPending_Init(&o->handler_job, pg, (BPending_handler)handler_job_hander, o);
+    
+    // have no work
+    o->tw_have = 0;
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    PacketRecvInterface_Free(&o->output);
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        OTPGenerator_Free(&o->otpgen);
+    }
+fail0:
+    return 0;
+}
+
+void SPProtoEncoder_Free (SPProtoEncoder *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free work
+    if (o->tw_have) {
+        BThreadWork_Free(&o->tw);
+    }
+    
+    // free handler job
+    BPending_Free(&o->handler_job);
+    
+    // free plaintext buffer
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params)) {
+        free(o->buf);
+    }
+    
+    // free output
+    PacketRecvInterface_Free(&o->output);
+    
+    // free encryptor
+    if (SPPROTO_HAVE_ENCRYPTION(o->sp_params) && o->have_encryption_key) {
+        BEncryption_Free(&o->encryptor);
+    }
+    
+    // free otp generator
+    if (SPPROTO_HAVE_OTP(o->sp_params)) {
+        OTPGenerator_Free(&o->otpgen);
+    }
+}
+
+PacketRecvInterface * SPProtoEncoder_GetOutput (SPProtoEncoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
+
+void SPProtoEncoder_SetEncryptionKey (SPProtoEncoder *o, uint8_t *encryption_key)
+{
+    ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // stop existing work
+    maybe_stop_work(o);
+    
+    // free encryptor
+    if (o->have_encryption_key) {
+        BEncryption_Free(&o->encryptor);
+    }
+    
+    // init encryptor
+    BEncryption_Init(&o->encryptor, BENCRYPTION_MODE_ENCRYPT, o->sp_params.encryption_mode, encryption_key);
+    
+    // have encryption key
+    o->have_encryption_key = 1;
+    
+    // possibly continue I/O
+    maybe_encode(o);
+}
+
+void SPProtoEncoder_RemoveEncryptionKey (SPProtoEncoder *o)
+{
+    ASSERT(SPPROTO_HAVE_ENCRYPTION(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // stop existing work
+    maybe_stop_work(o);
+    
+    if (o->have_encryption_key) {
+        // free encryptor
+        BEncryption_Free(&o->encryptor);
+        
+        // have no encryption key
+        o->have_encryption_key = 0;
+    }
+}
+
+void SPProtoEncoder_SetOTPSeed (SPProtoEncoder *o, uint16_t seed_id, uint8_t *key, uint8_t *iv)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // give seed to OTP generator
+    OTPGenerator_SetSeed(&o->otpgen, key, iv);
+    
+    // remember seed ID
+    o->otpgen_pending_seed_id = seed_id;
+}
+
+void SPProtoEncoder_RemoveOTPSeed (SPProtoEncoder *o)
+{
+    ASSERT(SPPROTO_HAVE_OTP(o->sp_params))
+    DebugObject_Access(&o->d_obj);
+    
+    // reset OTP generator
+    OTPGenerator_Reset(&o->otpgen);
+}
+
+void SPProtoEncoder_SetHandlers (SPProtoEncoder *o, SPProtoEncoder_handler handler, void *user)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    o->handler = handler;
+    o->user = user;
+}
diff --git a/external/badvpn_dns/client/SPProtoEncoder.h b/external/badvpn_dns/client/SPProtoEncoder.h
new file mode 100644
index 0000000..874f391
--- /dev/null
+++ b/external/badvpn_dns/client/SPProtoEncoder.h
@@ -0,0 +1,172 @@
+/**
+ * @file SPProtoEncoder.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object which encodes packets according to SPProto.
+ */
+
+#ifndef BADVPN_CLIENT_SPPROTOENCODER_H
+#define BADVPN_CLIENT_SPPROTOENCODER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <protocol/spproto.h>
+#include <base/DebugObject.h>
+#include <security/BEncryption.h>
+#include <security/OTPGenerator.h>
+#include <flow/PacketRecvInterface.h>
+#include <threadwork/BThreadWork.h>
+
+/**
+ * Event context handler called when the remaining number of
+ * OTPs equals the warning number after having encoded a packet.
+ * 
+ * @param user as in {@link SPProtoEncoder_Init}
+ */
+typedef void (*SPProtoEncoder_handler) (void *user);
+
+/**
+ * Object which encodes packets according to SPProto.
+ *
+ * Input is with {@link PacketRecvInterface}.
+ * Output is with {@link PacketRecvInterface}.
+ */
+typedef struct {
+    PacketRecvInterface *input;
+    struct spproto_security_params sp_params;
+    int otp_warning_count;
+    SPProtoEncoder_handler handler;
+    BThreadWorkDispatcher *twd;
+    void *user;
+    int hash_size;
+    int enc_block_size;
+    int enc_key_size;
+    OTPGenerator otpgen;
+    uint16_t otpgen_seed_id;
+    uint16_t otpgen_pending_seed_id;
+    int have_encryption_key;
+    BEncryption encryptor;
+    int input_mtu;
+    int output_mtu;
+    int in_len;
+    PacketRecvInterface output;
+    int out_have;
+    uint8_t *out;
+    uint8_t *buf;
+    BPending handler_job;
+    int tw_have;
+    BThreadWork tw;
+    uint16_t tw_seed_id;
+    otp_t tw_otp;
+    int tw_out_len;
+    DebugObject d_obj;
+} SPProtoEncoder;
+
+/**
+ * Initializes the object.
+ * The object is initialized in blocked state.
+ * {@link BSecurity_GlobalInitThreadSafe} must have been done if
+ * {@link BThreadWorkDispatcher_UsingThreads}(twd) = 1.
+ *
+ * @param o the object
+ * @param input input interface. Its MTU must not be too large, i.e. this must hold:
+ *              spproto_carrier_mtu_for_payload_mtu(sp_params, input MTU) >= 0
+ * @param sp_params SPProto security parameters
+ * @param otp_warning_count If using OTPs, after how many encoded packets to call the handler.
+ *                          In this case, must be >0 and <=sp_params.otp_num.
+ * @param pg pending group
+ * @param twd thread work dispatcher
+ * @return 1 on success, 0 on failure
+ */
+int SPProtoEncoder_Init (SPProtoEncoder *o, PacketRecvInterface *input, struct spproto_security_params sp_params, int otp_warning_count, BPendingGroup *pg, BThreadWorkDispatcher *twd) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void SPProtoEncoder_Free (SPProtoEncoder *o);
+
+/**
+ * Returns the output interface.
+ * The MTU of the output interface will depend on the input MTU and security parameters,
+ * that is spproto_carrier_mtu_for_payload_mtu(sp_params, input MTU).
+ *
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * SPProtoEncoder_GetOutput (SPProtoEncoder *o);
+
+/**
+ * Sets an encryption key to use.
+ * Encryption must be enabled.
+ *
+ * @param o the object
+ * @param encryption_key key to use
+ */
+void SPProtoEncoder_SetEncryptionKey (SPProtoEncoder *o, uint8_t *encryption_key);
+
+/**
+ * Removes an encryption key if one is configured.
+ * Encryption must be enabled.
+ *
+ * @param o the object
+ */
+void SPProtoEncoder_RemoveEncryptionKey (SPProtoEncoder *o);
+
+/**
+ * Sets an OTP seed to use.
+ * OTPs must be enabled.
+ *
+ * @param o the object
+ * @param seed_id seed identifier
+ * @param key OTP encryption key
+ * @param iv OTP initialization vector
+ */
+void SPProtoEncoder_SetOTPSeed (SPProtoEncoder *o, uint16_t seed_id, uint8_t *key, uint8_t *iv);
+
+/**
+ * Removes the OTP seed if one is configured.
+ * OTPs must be enabled.
+ *
+ * @param o the object
+ */
+void SPProtoEncoder_RemoveOTPSeed (SPProtoEncoder *o);
+
+/**
+ * Sets handlers.
+ *
+ * @param o the object
+ * @param handler OTP warning handler
+ * @param user value to pass to handler
+ */
+void SPProtoEncoder_SetHandlers (SPProtoEncoder *o, SPProtoEncoder_handler handler, void *user);
+
+#endif
diff --git a/external/badvpn_dns/client/SimpleStreamBuffer.c b/external/badvpn_dns/client/SimpleStreamBuffer.c
new file mode 100644
index 0000000..74448cb
--- /dev/null
+++ b/external/badvpn_dns/client/SimpleStreamBuffer.c
@@ -0,0 +1,144 @@
+/**
+ * @file SimpleStreamBuffer.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stddef.h>
+
+#include <misc/balloc.h>
+#include <misc/minmax.h>
+
+#include "SimpleStreamBuffer.h"
+
+static void try_output (SimpleStreamBuffer *o)
+{
+    ASSERT(o->output_data_len > 0)
+    
+    // calculate number of bytes to output
+    int bytes = bmin_int(o->output_data_len, o->buf_used);
+    if (bytes == 0) {
+        return;
+    }
+    
+    // copy bytes to output
+    memcpy(o->output_data, o->buf, bytes);
+    
+    // shift buffer
+    memmove(o->buf, o->buf + bytes, o->buf_used - bytes);
+    o->buf_used -= bytes;
+    
+    // forget data
+    o->output_data_len = -1;
+    
+    // done
+    StreamRecvInterface_Done(&o->output, bytes);
+}
+
+static void output_handler_recv (SimpleStreamBuffer *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->output_data_len == -1)
+    ASSERT(data)
+    ASSERT(data_len > 0)
+    
+    // remember data
+    o->output_data = data;
+    o->output_data_len = data_len;
+    
+    try_output(o);
+}
+
+int SimpleStreamBuffer_Init (SimpleStreamBuffer *o, int buf_size, BPendingGroup *pg)
+{
+    ASSERT(buf_size > 0)
+    
+    // init arguments
+    o->buf_size = buf_size;
+    
+    // init output
+    StreamRecvInterface_Init(&o->output, (StreamRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    // allocate buffer
+    if (!(o->buf = (uint8_t *)BAlloc(buf_size))) {
+        goto fail1;
+    }
+    
+    // init buffer state
+    o->buf_used = 0;
+    
+    // set no output data
+    o->output_data_len = -1;
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail1:
+    StreamRecvInterface_Free(&o->output);
+    return 0;
+}
+
+void SimpleStreamBuffer_Free (SimpleStreamBuffer *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free buffer
+    BFree(o->buf);
+    
+    // free output
+    StreamRecvInterface_Free(&o->output);
+}
+
+StreamRecvInterface * SimpleStreamBuffer_GetOutput (SimpleStreamBuffer *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
+
+int SimpleStreamBuffer_Write (SimpleStreamBuffer *o, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(data_len >= 0)
+    
+    if (data_len > o->buf_size - o->buf_used) {
+        return 0;
+    }
+    
+    // copy to buffer
+    memcpy(o->buf + o->buf_used, data, data_len);
+    
+    // update buffer state
+    o->buf_used += data_len;
+    
+    // continue outputting
+    if (o->output_data_len > 0) {
+        try_output(o);
+    }
+    
+    return 1;
+}
diff --git a/external/badvpn_dns/client/SimpleStreamBuffer.h b/external/badvpn_dns/client/SimpleStreamBuffer.h
new file mode 100644
index 0000000..31a55f7
--- /dev/null
+++ b/external/badvpn_dns/client/SimpleStreamBuffer.h
@@ -0,0 +1,52 @@
+/**
+ * @file SimpleStreamBuffer.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_SIMPLESTREAMBUFFER_H
+#define BADVPN_SIMPLESTREAMBUFFER_H
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <flow/StreamRecvInterface.h>
+
+typedef struct {
+    int buf_size;
+    StreamRecvInterface output;
+    uint8_t *buf;
+    int buf_used;
+    uint8_t *output_data;
+    int output_data_len;
+    DebugObject d_obj;
+} SimpleStreamBuffer;
+
+int SimpleStreamBuffer_Init (SimpleStreamBuffer *o, int buf_size, BPendingGroup *pg) WARN_UNUSED;
+void SimpleStreamBuffer_Free (SimpleStreamBuffer *o);
+StreamRecvInterface * SimpleStreamBuffer_GetOutput (SimpleStreamBuffer *o);
+int SimpleStreamBuffer_Write (SimpleStreamBuffer *o, uint8_t *data, int data_len) WARN_UNUSED;
+
+#endif
diff --git a/external/badvpn_dns/client/SinglePacketSource.c b/external/badvpn_dns/client/SinglePacketSource.c
new file mode 100644
index 0000000..1c6a573
--- /dev/null
+++ b/external/badvpn_dns/client/SinglePacketSource.c
@@ -0,0 +1,85 @@
+/**
+ * @file SinglePacketSource.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include <misc/debug.h>
+
+#include "SinglePacketSource.h"
+
+static void output_handler_recv (SinglePacketSource *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // if we already sent one packet, stop
+    if (o->sent) {
+        return;
+    }
+    
+    // set sent
+    o->sent = 1;
+    
+    // write packet
+    memcpy(data, o->packet, o->packet_len);
+    
+    // done
+    PacketRecvInterface_Done(&o->output, o->packet_len);
+}
+
+void SinglePacketSource_Init (SinglePacketSource *o, uint8_t *packet, int packet_len, BPendingGroup *pg)
+{
+    ASSERT(packet_len >= 0)
+    
+    // init arguments
+    o->packet = packet;
+    o->packet_len = packet_len;
+    
+    // set not sent
+    o->sent = 0;
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, o->packet_len, (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void SinglePacketSource_Free (SinglePacketSource *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free output
+    PacketRecvInterface_Free(&o->output);
+}
+
+PacketRecvInterface * SinglePacketSource_GetOutput (SinglePacketSource *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
diff --git a/external/badvpn_dns/client/SinglePacketSource.h b/external/badvpn_dns/client/SinglePacketSource.h
new file mode 100644
index 0000000..85ca426
--- /dev/null
+++ b/external/badvpn_dns/client/SinglePacketSource.h
@@ -0,0 +1,73 @@
+/**
+ * @file SinglePacketSource.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_SINGLEPACKETSOURCE_H
+#define BADVPN_SINGLEPACKETSOURCE_H
+
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * An object which provides a single packet through {@link PacketRecvInterface}.
+ */
+typedef struct {
+    uint8_t *packet;
+    int packet_len;
+    int sent;
+    PacketRecvInterface output;
+    DebugObject d_obj;
+} SinglePacketSource;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param packet packet to provide to the output. Must stay available until the packet is provided.
+ * @param packet_len length of packet. Must be >=0.
+ * @param pg pending group we live in
+ */
+void SinglePacketSource_Init (SinglePacketSource *o, uint8_t *packet, int packet_len, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void SinglePacketSource_Free (SinglePacketSource *o);
+
+/**
+ * Returns the output interface.
+ * The MTU of the interface will be packet_len.
+ * 
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * SinglePacketSource_GetOutput (SinglePacketSource *o);
+
+#endif
diff --git a/external/badvpn_dns/client/StreamPeerIO.c b/external/badvpn_dns/client/StreamPeerIO.c
new file mode 100644
index 0000000..3113c3b
--- /dev/null
+++ b/external/badvpn_dns/client/StreamPeerIO.c
@@ -0,0 +1,712 @@
+/**
+ * @file StreamPeerIO.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include <ssl.h>
+#include <sslerr.h>
+
+#include <misc/offset.h>
+#include <misc/byteorder.h>
+
+#include <client/StreamPeerIO.h>
+
+#include <generated/blog_channel_StreamPeerIO.h>
+
+#define MODE_NONE 0
+#define MODE_CONNECT 1
+#define MODE_LISTEN 2
+
+#define CONNECT_STATE_CONNECTING 0
+#define CONNECT_STATE_HANDSHAKE 1
+#define CONNECT_STATE_SENDING 2
+#define CONNECT_STATE_SENT 3
+#define CONNECT_STATE_FINISHED 4
+
+#define LISTEN_STATE_LISTENER 0
+#define LISTEN_STATE_GOTCLIENT 1
+#define LISTEN_STATE_FINISHED 2
+
+#define PeerLog(_o, ...) BLog_LogViaFunc((_o)->logfunc, (_o)->user, BLOG_CURRENT_CHANNEL, __VA_ARGS__)
+
+static void decoder_handler_error (StreamPeerIO *pio);
+static void connector_handler (StreamPeerIO *pio, int is_error);
+static void connection_handler (StreamPeerIO *pio, int event);
+static void connect_sslcon_handler (StreamPeerIO *pio, int event);
+static void pwsender_handler (StreamPeerIO *pio);
+static void listener_handler_client (StreamPeerIO *pio, sslsocket *sock);
+static int init_io (StreamPeerIO *pio, sslsocket *sock);
+static void free_io (StreamPeerIO *pio);
+static void sslcon_handler (StreamPeerIO *pio, int event);
+static SECStatus client_auth_certificate_callback (StreamPeerIO *pio, PRFileDesc *fd, PRBool checkSig, PRBool isServer);
+static SECStatus client_client_auth_data_callback (StreamPeerIO *pio, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey);
+static int compare_certificate (StreamPeerIO *pio, CERTCertificate *cert);
+static void reset_state (StreamPeerIO *pio);
+static void reset_and_report_error (StreamPeerIO *pio);
+
+void decoder_handler_error (StreamPeerIO *pio)
+{
+    DebugObject_Access(&pio->d_obj);
+    
+    PeerLog(pio, BLOG_ERROR, "decoder error");
+    
+    reset_and_report_error(pio);
+    return;
+}
+
+void connector_handler (StreamPeerIO *pio, int is_error)
+{
+    DebugObject_Access(&pio->d_obj);
+    ASSERT(pio->mode == MODE_CONNECT)
+    ASSERT(pio->connect.state == CONNECT_STATE_CONNECTING)
+    
+    // check connection result
+    if (is_error) {
+        PeerLog(pio, BLOG_NOTICE, "connection failed");
+        goto fail0;
+    }
+    
+    // init connection
+    if (!BConnection_Init(&pio->connect.sock.con, BConnection_source_connector(&pio->connect.connector), pio->reactor, pio, (BConnection_handler)connection_handler)) {
+        PeerLog(pio, BLOG_ERROR, "BConnection_Init failed");
+        goto fail0;
+    }
+    
+    if (pio->ssl) {
+        // init connection interfaces
+        BConnection_SendAsync_Init(&pio->connect.sock.con);
+        BConnection_RecvAsync_Init(&pio->connect.sock.con);
+        
+        // create bottom NSPR file descriptor
+        if (!BSSLConnection_MakeBackend(&pio->connect.sock.bottom_prfd, BConnection_SendAsync_GetIf(&pio->connect.sock.con), BConnection_RecvAsync_GetIf(&pio->connect.sock.con), pio->twd, pio->ssl_flags)) {
+            PeerLog(pio, BLOG_ERROR, "BSSLConnection_MakeBackend failed");
+            goto fail1;
+        }
+        
+        // create SSL file descriptor from the bottom NSPR file descriptor
+        if (!(pio->connect.sock.ssl_prfd = SSL_ImportFD(NULL, &pio->connect.sock.bottom_prfd))) {
+            ASSERT_FORCE(PR_Close(&pio->connect.sock.bottom_prfd) == PR_SUCCESS)
+            goto fail1;
+        }
+        
+        // set client mode
+        if (SSL_ResetHandshake(pio->connect.sock.ssl_prfd, PR_FALSE) != SECSuccess) {
+            PeerLog(pio, BLOG_ERROR, "SSL_ResetHandshake failed");
+            goto fail2;
+        }
+        
+        // set verify peer certificate hook
+        if (SSL_AuthCertificateHook(pio->connect.sock.ssl_prfd, (SSLAuthCertificate)client_auth_certificate_callback, pio) != SECSuccess) {
+            PeerLog(pio, BLOG_ERROR, "SSL_AuthCertificateHook failed");
+            goto fail2;
+        }
+        
+        // set client certificate callback
+        if (SSL_GetClientAuthDataHook(pio->connect.sock.ssl_prfd, (SSLGetClientAuthData)client_client_auth_data_callback, pio) != SECSuccess) {
+            PeerLog(pio, BLOG_ERROR, "SSL_GetClientAuthDataHook failed");
+            goto fail2;
+        }
+        
+        // init BSSLConnection
+        BSSLConnection_Init(&pio->connect.sslcon, pio->connect.sock.ssl_prfd, 1, BReactor_PendingGroup(pio->reactor), pio, (BSSLConnection_handler)connect_sslcon_handler);
+        
+        // change state
+        pio->connect.state = CONNECT_STATE_HANDSHAKE;
+    } else {
+        // init connection send interface
+        BConnection_SendAsync_Init(&pio->connect.sock.con);
+        
+        // init password sender
+        SingleStreamSender_Init(&pio->connect.pwsender, (uint8_t *)&pio->connect.password, sizeof(pio->connect.password), BConnection_SendAsync_GetIf(&pio->connect.sock.con), BReactor_PendingGroup(pio->reactor), pio, (SingleStreamSender_handler)pwsender_handler);
+        
+        // change state
+        pio->connect.state = CONNECT_STATE_SENDING;
+    }
+    
+    return;
+
+    if (pio->ssl) {
+fail2:
+        ASSERT_FORCE(PR_Close(pio->connect.sock.ssl_prfd) == PR_SUCCESS)
+fail1:
+        BConnection_RecvAsync_Free(&pio->connect.sock.con);
+        BConnection_SendAsync_Free(&pio->connect.sock.con);
+    }
+    BConnection_Free(&pio->connect.sock.con);
+fail0:
+    reset_and_report_error(pio);
+    return;
+}
+
+void connection_handler (StreamPeerIO *pio, int event)
+{
+    DebugObject_Access(&pio->d_obj);
+    ASSERT(pio->mode == MODE_CONNECT || pio->mode == MODE_LISTEN)
+    ASSERT(!(pio->mode == MODE_CONNECT) || pio->connect.state >= CONNECT_STATE_HANDSHAKE)
+    ASSERT(!(pio->mode == MODE_LISTEN) || pio->listen.state >= LISTEN_STATE_FINISHED)
+    
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        PeerLog(pio, BLOG_NOTICE, "connection closed");
+    } else {
+        PeerLog(pio, BLOG_NOTICE, "connection error");
+    }
+    
+    reset_and_report_error(pio);
+    return;
+}
+
+void connect_sslcon_handler (StreamPeerIO *pio, int event)
+{
+    DebugObject_Access(&pio->d_obj);
+    ASSERT(pio->ssl)
+    ASSERT(pio->mode == MODE_CONNECT)
+    ASSERT(pio->connect.state == CONNECT_STATE_HANDSHAKE || pio->connect.state == CONNECT_STATE_SENDING)
+    ASSERT(event == BSSLCONNECTION_EVENT_UP || event == BSSLCONNECTION_EVENT_ERROR)
+    
+    if (event == BSSLCONNECTION_EVENT_ERROR) {
+        PeerLog(pio, BLOG_NOTICE, "SSL error");
+        
+        reset_and_report_error(pio);
+        return;
+    }
+    
+    // handshake complete
+    ASSERT(pio->connect.state == CONNECT_STATE_HANDSHAKE)
+    
+    // remove client certificate callback
+    if (SSL_GetClientAuthDataHook(pio->connect.sock.ssl_prfd, NULL, NULL) != SECSuccess) {
+        PeerLog(pio, BLOG_ERROR, "SSL_GetClientAuthDataHook failed");
+        goto fail0;
+    }
+    
+    // remove verify peer certificate callback
+    if (SSL_AuthCertificateHook(pio->connect.sock.ssl_prfd, NULL, NULL) != SECSuccess) {
+        PeerLog(pio, BLOG_ERROR, "SSL_AuthCertificateHook failed");
+        goto fail0;
+    }
+    
+    // init password sender
+    SingleStreamSender_Init(&pio->connect.pwsender, (uint8_t *)&pio->connect.password, sizeof(pio->connect.password), BSSLConnection_GetSendIf(&pio->connect.sslcon), BReactor_PendingGroup(pio->reactor), pio, (SingleStreamSender_handler)pwsender_handler);
+    
+    // change state
+    pio->connect.state = CONNECT_STATE_SENDING;
+    
+    return;
+    
+fail0:
+    reset_and_report_error(pio);
+    return;
+}
+
+void pwsender_handler (StreamPeerIO *pio)
+{
+    DebugObject_Access(&pio->d_obj);
+    ASSERT(pio->mode == MODE_CONNECT)
+    ASSERT(pio->connect.state == CONNECT_STATE_SENDING)
+    
+    // stop using any buffers before they get freed
+    if (pio->ssl) {
+        BSSLConnection_ReleaseBuffers(&pio->connect.sslcon);
+    }
+    
+    // free password sender
+    SingleStreamSender_Free(&pio->connect.pwsender);
+    
+    if (pio->ssl) {
+        // free BSSLConnection (we used the send interface)
+        BSSLConnection_Free(&pio->connect.sslcon);
+    } else {
+        // init connection send interface
+        BConnection_SendAsync_Free(&pio->connect.sock.con);
+    }
+    
+    // change state
+    pio->connect.state = CONNECT_STATE_SENT;
+    
+    // setup i/o
+    if (!init_io(pio, &pio->connect.sock)) {
+        goto fail0;
+    }
+    
+    // change state
+    pio->connect.state = CONNECT_STATE_FINISHED;
+    
+    return;
+    
+fail0:
+    reset_and_report_error(pio);
+    return;
+}
+
+void listener_handler_client (StreamPeerIO *pio, sslsocket *sock)
+{
+    DebugObject_Access(&pio->d_obj);
+    ASSERT(pio->mode == MODE_LISTEN)
+    ASSERT(pio->listen.state == LISTEN_STATE_LISTENER)
+    
+    // remember socket
+    pio->listen.sock = sock;
+    
+    // set connection handler
+    BConnection_SetHandlers(&pio->listen.sock->con, pio, (BConnection_handler)connection_handler);
+    
+    // change state
+    pio->listen.state = LISTEN_STATE_GOTCLIENT;
+    
+    // check ceritficate
+    if (pio->ssl) {
+        CERTCertificate *peer_cert = SSL_PeerCertificate(pio->listen.sock->ssl_prfd);
+        if (!peer_cert) {
+            PeerLog(pio, BLOG_ERROR, "SSL_PeerCertificate failed");
+            goto fail0;
+        }
+        
+        // compare certificate to the one provided by the server
+        if (!compare_certificate(pio, peer_cert)) {
+            CERT_DestroyCertificate(peer_cert);
+            goto fail0;
+        }
+        
+        CERT_DestroyCertificate(peer_cert);
+    }
+    
+    // setup i/o
+    if (!init_io(pio, pio->listen.sock)) {
+        goto fail0;
+    }
+    
+    // change state
+    pio->listen.state = LISTEN_STATE_FINISHED;
+    
+    return;
+    
+fail0:
+    reset_and_report_error(pio);
+    return;
+}
+
+int init_io (StreamPeerIO *pio, sslsocket *sock)
+{
+    ASSERT(!pio->sock)
+    
+    // limit socket send buffer, else our scheduling is pointless
+    if (pio->sock_sndbuf > 0) {
+        if (!BConnection_SetSendBuffer(&sock->con, pio->sock_sndbuf)) {
+            PeerLog(pio, BLOG_WARNING, "BConnection_SetSendBuffer failed");
+        }
+    }
+    
+    if (pio->ssl) {
+        // init BSSLConnection
+        BSSLConnection_Init(&pio->sslcon, sock->ssl_prfd, 0, BReactor_PendingGroup(pio->reactor), pio, (BSSLConnection_handler)sslcon_handler);
+    } else {
+        // init connection interfaces
+        BConnection_SendAsync_Init(&sock->con);
+        BConnection_RecvAsync_Init(&sock->con);
+    }
+    
+    StreamPassInterface *send_if = (pio->ssl ? BSSLConnection_GetSendIf(&pio->sslcon) : BConnection_SendAsync_GetIf(&sock->con));
+    StreamRecvInterface *recv_if = (pio->ssl ? BSSLConnection_GetRecvIf(&pio->sslcon) : BConnection_RecvAsync_GetIf(&sock->con));
+    
+    // init receiving
+    StreamRecvConnector_ConnectInput(&pio->input_connector, recv_if);
+    
+    // init sending
+    PacketStreamSender_Init(&pio->output_pss, send_if, PACKETPROTO_ENCLEN(pio->payload_mtu), BReactor_PendingGroup(pio->reactor));
+    PacketPassConnector_ConnectOutput(&pio->output_connector, PacketStreamSender_GetInput(&pio->output_pss));
+    
+    pio->sock = sock;
+    
+    return 1;
+}
+
+void free_io (StreamPeerIO *pio)
+{
+    ASSERT(pio->sock)
+    
+    // stop using any buffers before they get freed
+    if (pio->ssl) {
+        BSSLConnection_ReleaseBuffers(&pio->sslcon);
+    }
+    
+    // reset decoder
+    PacketProtoDecoder_Reset(&pio->input_decoder);
+    
+    // free sending
+    PacketPassConnector_DisconnectOutput(&pio->output_connector);
+    PacketStreamSender_Free(&pio->output_pss);
+    
+    // free receiving
+    StreamRecvConnector_DisconnectInput(&pio->input_connector);
+    
+    if (pio->ssl) {
+        // free BSSLConnection
+        BSSLConnection_Free(&pio->sslcon);
+    } else {
+        // free connection interfaces
+        BConnection_RecvAsync_Free(&pio->sock->con);
+        BConnection_SendAsync_Free(&pio->sock->con);
+    }
+    
+    pio->sock = NULL;
+}
+
+void sslcon_handler (StreamPeerIO *pio, int event)
+{
+    DebugObject_Access(&pio->d_obj);
+    ASSERT(pio->ssl)
+    ASSERT(pio->mode == MODE_CONNECT || pio->mode == MODE_LISTEN)
+    ASSERT(!(pio->mode == MODE_CONNECT) || pio->connect.state == CONNECT_STATE_FINISHED)
+    ASSERT(!(pio->mode == MODE_LISTEN) || pio->listen.state == LISTEN_STATE_FINISHED)
+    ASSERT(event == BSSLCONNECTION_EVENT_ERROR)
+    
+    PeerLog(pio, BLOG_NOTICE, "SSL error");
+    
+    reset_and_report_error(pio);
+    return;
+}
+
+SECStatus client_auth_certificate_callback (StreamPeerIO *pio, PRFileDesc *fd, PRBool checkSig, PRBool isServer)
+{
+    ASSERT(pio->ssl)
+    ASSERT(pio->mode == MODE_CONNECT)
+    ASSERT(pio->connect.state == CONNECT_STATE_HANDSHAKE)
+    DebugObject_Access(&pio->d_obj);
+    
+    // This callback is used to bypass checking the server's domain name, as peers
+    // don't have domain names. We byte-compare the certificate to the one reported
+    // by the server anyway.
+    
+    SECStatus ret = SECFailure;
+    
+    CERTCertificate *server_cert = SSL_PeerCertificate(pio->connect.sock.ssl_prfd);
+    if (!server_cert) {
+        PeerLog(pio, BLOG_ERROR, "SSL_PeerCertificate failed");
+        PORT_SetError(SSL_ERROR_BAD_CERTIFICATE);
+        goto fail1;
+    }
+    
+    if (CERT_VerifyCertNow(CERT_GetDefaultCertDB(), server_cert, PR_TRUE, certUsageSSLServer, SSL_RevealPinArg(pio->connect.sock.ssl_prfd)) != SECSuccess) {
+        goto fail2;
+    }
+    
+    // compare to certificate provided by the server
+    if (!compare_certificate(pio, server_cert)) {
+        PORT_SetError(SSL_ERROR_BAD_CERTIFICATE);
+        goto fail2;
+    }
+    
+    ret = SECSuccess;
+    
+fail2:
+    CERT_DestroyCertificate(server_cert);
+fail1:
+    return ret;
+}
+
+SECStatus client_client_auth_data_callback (StreamPeerIO *pio, PRFileDesc *fd, CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey)
+{
+    ASSERT(pio->ssl)
+    ASSERT(pio->mode == MODE_CONNECT)
+    ASSERT(pio->connect.state == CONNECT_STATE_HANDSHAKE)
+    DebugObject_Access(&pio->d_obj);
+    
+    CERTCertificate *cert = CERT_DupCertificate(pio->connect.ssl_cert);
+    if (!cert) {
+        PeerLog(pio, BLOG_ERROR, "CERT_DupCertificate failed");
+        goto fail0;
+    }
+    
+    SECKEYPrivateKey *key = SECKEY_CopyPrivateKey(pio->connect.ssl_key);
+    if (!key) {
+        PeerLog(pio, BLOG_ERROR, "SECKEY_CopyPrivateKey failed");
+        goto fail1;
+    }
+    
+    *pRetCert = cert;
+    *pRetKey = key;
+    return SECSuccess;
+    
+fail1:
+    CERT_DestroyCertificate(cert);
+fail0:
+    return SECFailure;
+}
+
+int compare_certificate (StreamPeerIO *pio, CERTCertificate *cert)
+{
+    ASSERT(pio->ssl)
+    
+    SECItem der = cert->derCert;
+    if (der.len != pio->ssl_peer_cert_len || memcmp(der.data, pio->ssl_peer_cert, der.len)) {
+        PeerLog(pio, BLOG_NOTICE, "Client certificate doesn't match");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void reset_state (StreamPeerIO *pio)
+{
+    // free resources
+    switch (pio->mode) {
+        case MODE_NONE:
+            break;
+        case MODE_LISTEN:
+            switch (pio->listen.state) {
+                case LISTEN_STATE_FINISHED:
+                    free_io(pio);
+                case LISTEN_STATE_GOTCLIENT:
+                    if (pio->ssl) {
+                        ASSERT_FORCE(PR_Close(pio->listen.sock->ssl_prfd) == PR_SUCCESS)
+                        BConnection_RecvAsync_Free(&pio->listen.sock->con);
+                        BConnection_SendAsync_Free(&pio->listen.sock->con);
+                    }
+                    BConnection_Free(&pio->listen.sock->con);
+                    free(pio->listen.sock);
+                case LISTEN_STATE_LISTENER:
+                    if (pio->listen.state == LISTEN_STATE_LISTENER) {
+                        PasswordListener_RemoveEntry(pio->listen.listener, &pio->listen.pwentry);
+                    }
+                    break;
+                default:
+                    ASSERT(0);
+            }
+            break;
+        case MODE_CONNECT:
+            switch (pio->connect.state) {
+                case CONNECT_STATE_FINISHED:
+                    free_io(pio);
+                case CONNECT_STATE_SENT:
+                case CONNECT_STATE_SENDING:
+                    if (pio->connect.state == CONNECT_STATE_SENDING) {
+                        if (pio->ssl) {
+                            BSSLConnection_ReleaseBuffers(&pio->connect.sslcon);
+                        }
+                        SingleStreamSender_Free(&pio->connect.pwsender);
+                        if (!pio->ssl) {
+                            BConnection_SendAsync_Free(&pio->connect.sock.con);
+                        }
+                    }
+                case CONNECT_STATE_HANDSHAKE:
+                    if (pio->ssl) {
+                        if (pio->connect.state == CONNECT_STATE_HANDSHAKE || pio->connect.state == CONNECT_STATE_SENDING) {
+                            BSSLConnection_Free(&pio->connect.sslcon);
+                        }
+                        ASSERT_FORCE(PR_Close(pio->connect.sock.ssl_prfd) == PR_SUCCESS)
+                        BConnection_RecvAsync_Free(&pio->connect.sock.con);
+                        BConnection_SendAsync_Free(&pio->connect.sock.con);
+                    }
+                    BConnection_Free(&pio->connect.sock.con);
+                case CONNECT_STATE_CONNECTING:
+                    BConnector_Free(&pio->connect.connector);
+                    break;
+                default:
+                    ASSERT(0);
+            }
+            break;
+        default:
+            ASSERT(0);
+    }
+    
+    // set mode none
+    pio->mode = MODE_NONE;
+    
+    ASSERT(!pio->sock)
+}
+
+void reset_and_report_error (StreamPeerIO *pio)
+{
+    reset_state(pio);
+    
+    pio->handler_error(pio->user);
+    return;
+}
+
+int StreamPeerIO_Init (
+    StreamPeerIO *pio,
+    BReactor *reactor,
+    BThreadWorkDispatcher *twd,
+    int ssl,
+    int ssl_flags,
+    uint8_t *ssl_peer_cert,
+    int ssl_peer_cert_len,
+    int payload_mtu,
+    int sock_sndbuf,
+    PacketPassInterface *user_recv_if,
+    BLog_logfunc logfunc,
+    StreamPeerIO_handler_error handler_error,
+    void *user
+)
+{
+    ASSERT(ssl == 0 || ssl == 1)
+    ASSERT(payload_mtu >= 0)
+    ASSERT(PacketPassInterface_GetMTU(user_recv_if) >= payload_mtu)
+    ASSERT(handler_error)
+    
+    // init arguments
+    pio->reactor = reactor;
+    pio->twd = twd;
+    pio->ssl = ssl;
+    if (pio->ssl) {
+        pio->ssl_flags = ssl_flags;
+        pio->ssl_peer_cert = ssl_peer_cert;
+        pio->ssl_peer_cert_len = ssl_peer_cert_len;
+    }
+    pio->payload_mtu = payload_mtu;
+    pio->sock_sndbuf = sock_sndbuf;
+    pio->logfunc = logfunc;
+    pio->handler_error = handler_error;
+    pio->user = user;
+    
+    // check payload MTU
+    if (pio->payload_mtu > PACKETPROTO_MAXPAYLOAD) {
+        PeerLog(pio, BLOG_ERROR, "payload MTU is too large");
+        goto fail0;
+    }
+    
+    // init receiveing objects
+    StreamRecvConnector_Init(&pio->input_connector, BReactor_PendingGroup(pio->reactor));
+    if (!PacketProtoDecoder_Init(&pio->input_decoder, StreamRecvConnector_GetOutput(&pio->input_connector), user_recv_if, BReactor_PendingGroup(pio->reactor), pio,
+        (PacketProtoDecoder_handler_error)decoder_handler_error
+    )) {
+        PeerLog(pio, BLOG_ERROR, "FlowErrorDomain_Init failed");
+        goto fail1;
+    }
+    
+    // init sending objects
+    PacketCopier_Init(&pio->output_user_copier, pio->payload_mtu, BReactor_PendingGroup(pio->reactor));
+    PacketProtoEncoder_Init(&pio->output_user_ppe, PacketCopier_GetOutput(&pio->output_user_copier), BReactor_PendingGroup(pio->reactor));
+    PacketPassConnector_Init(&pio->output_connector, PACKETPROTO_ENCLEN(pio->payload_mtu), BReactor_PendingGroup(pio->reactor));
+    if (!SinglePacketBuffer_Init(&pio->output_user_spb, PacketProtoEncoder_GetOutput(&pio->output_user_ppe), PacketPassConnector_GetInput(&pio->output_connector), BReactor_PendingGroup(pio->reactor))) {
+        PeerLog(pio, BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail2;
+    }
+    
+    // set mode none
+    pio->mode = MODE_NONE;
+    
+    // set no socket
+    pio->sock = NULL;
+    
+    DebugObject_Init(&pio->d_obj);
+    return 1;
+    
+fail2:
+    PacketPassConnector_Free(&pio->output_connector);
+    PacketProtoEncoder_Free(&pio->output_user_ppe);
+    PacketCopier_Free(&pio->output_user_copier);
+    PacketProtoDecoder_Free(&pio->input_decoder);
+fail1:
+    StreamRecvConnector_Free(&pio->input_connector);
+fail0:
+    return 0;
+}
+
+void StreamPeerIO_Free (StreamPeerIO *pio)
+{
+    DebugObject_Free(&pio->d_obj);
+
+    // reset state
+    reset_state(pio);
+    
+    // free sending objects
+    SinglePacketBuffer_Free(&pio->output_user_spb);
+    PacketPassConnector_Free(&pio->output_connector);
+    PacketProtoEncoder_Free(&pio->output_user_ppe);
+    PacketCopier_Free(&pio->output_user_copier);
+    
+    // free receiveing objects
+    PacketProtoDecoder_Free(&pio->input_decoder);
+    StreamRecvConnector_Free(&pio->input_connector);
+}
+
+PacketPassInterface * StreamPeerIO_GetSendInput (StreamPeerIO *pio)
+{
+    DebugObject_Access(&pio->d_obj);
+    
+    return PacketCopier_GetInput(&pio->output_user_copier);
+}
+
+int StreamPeerIO_Connect (StreamPeerIO *pio, BAddr addr, uint64_t password, CERTCertificate *ssl_cert, SECKEYPrivateKey *ssl_key)
+{
+    DebugObject_Access(&pio->d_obj);
+    
+    // reset state
+    reset_state(pio);
+    
+    // check address
+    if (!BConnection_AddressSupported(addr)) {
+        PeerLog(pio, BLOG_ERROR, "BConnection_AddressSupported failed");
+        goto fail0;
+    }
+    
+    // init connector
+    if (!BConnector_Init(&pio->connect.connector, addr, pio->reactor, pio, (BConnector_handler)connector_handler)) {
+        PeerLog(pio, BLOG_ERROR, "BConnector_Init failed");
+        goto fail0;
+    }
+    
+    // remember data
+    if (pio->ssl) {
+        pio->connect.ssl_cert = ssl_cert;
+        pio->connect.ssl_key = ssl_key;
+    }
+    pio->connect.password = htol64(password);
+    
+    // set state
+    pio->mode = MODE_CONNECT;
+    pio->connect.state = CONNECT_STATE_CONNECTING;
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void StreamPeerIO_Listen (StreamPeerIO *pio, PasswordListener *listener, uint64_t *password)
+{
+    DebugObject_Access(&pio->d_obj);
+    ASSERT(listener->ssl == pio->ssl)
+    
+    // reset state
+    reset_state(pio);
+    
+    // add PasswordListener entry
+    uint64_t newpass = PasswordListener_AddEntry(listener, &pio->listen.pwentry, (PasswordListener_handler_client)listener_handler_client, pio);
+    
+    // remember data
+    pio->listen.listener = listener;
+    
+    // set state
+    pio->mode = MODE_LISTEN;
+    pio->listen.state = LISTEN_STATE_LISTENER;
+    
+    *password = newpass;
+}
diff --git a/external/badvpn_dns/client/StreamPeerIO.h b/external/badvpn_dns/client/StreamPeerIO.h
new file mode 100644
index 0000000..0b6b260
--- /dev/null
+++ b/external/badvpn_dns/client/StreamPeerIO.h
@@ -0,0 +1,222 @@
+/**
+ * @file StreamPeerIO.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object used for communicating with a peer over TCP.
+ */
+
+#ifndef BADVPN_CLIENT_STREAMPEERIO_H
+#define BADVPN_CLIENT_STREAMPEERIO_H
+
+#include <stdint.h>
+
+#include <cert.h>
+#include <keyhi.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BConnection.h>
+#include <structure/LinkedList1.h>
+#include <flow/PacketProtoDecoder.h>
+#include <flow/PacketStreamSender.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketProtoEncoder.h>
+#include <flow/PacketCopier.h>
+#include <flow/PacketPassConnector.h>
+#include <flow/StreamRecvConnector.h>
+#include <flow/SingleStreamSender.h>
+#include <client/PasswordListener.h>
+
+/**
+ * Callback function invoked when an error occurs with the peer connection.
+ * The object has entered default state.
+ * May be called from within a sending Send call.
+ *
+ * @param user value given to {@link StreamPeerIO_Init}.
+ */
+typedef void (*StreamPeerIO_handler_error) (void *user);
+
+/**
+ * Object used for communicating with a peer over TCP.
+ * The object has a logical state which can be one of the following:
+ *     - default state
+ *     - listening state
+ *     - connecting state
+ */
+typedef struct {
+    // common arguments
+    BReactor *reactor;
+    BThreadWorkDispatcher *twd;
+    int ssl;
+    int ssl_flags;
+    uint8_t *ssl_peer_cert;
+    int ssl_peer_cert_len;
+    int payload_mtu;
+    int sock_sndbuf;
+    BLog_logfunc logfunc;
+    StreamPeerIO_handler_error handler_error;
+    void *user;
+    
+    // persistent I/O modules
+    
+    // base sending objects
+    PacketCopier output_user_copier;
+    PacketProtoEncoder output_user_ppe;
+    SinglePacketBuffer output_user_spb;
+    PacketPassConnector output_connector;
+    
+    // receiving objects
+    StreamRecvConnector input_connector;
+    PacketProtoDecoder input_decoder;
+    
+    // connection side
+    int mode;
+    
+    union {
+        // listening data
+        struct {
+            int state;
+            PasswordListener *listener;
+            PasswordListener_pwentry pwentry;
+            sslsocket *sock;
+        } listen;
+        // connecting data
+        struct {
+            int state;
+            CERTCertificate *ssl_cert;
+            SECKEYPrivateKey *ssl_key;
+            BConnector connector;
+            sslsocket sock;
+            BSSLConnection sslcon;
+            uint64_t password;
+            SingleStreamSender pwsender;
+        } connect;
+    };
+    
+    // socket data
+    sslsocket *sock;
+    BSSLConnection sslcon;
+    
+    // sending objects
+    PacketStreamSender output_pss;
+    
+    DebugObject d_obj;
+} StreamPeerIO;
+
+/**
+ * Initializes the object.
+ * The object is initialized in default state.
+ * {@link BLog_Init} must have been done.
+ * {@link BNetwork_GlobalInit} must have been done.
+ * {@link BSSLConnection_GlobalInit} must have been done if using SSL.
+ *
+ * @param pio the object
+ * @param reactor reactor we live in
+ * @param twd thread work dispatcher. May be NULL if ssl_flags does not request performing SSL
+ *            operations in threads.
+ * @param ssl if nonzero, SSL will be used for peer connection
+ * @param ssl_flags flags passed down to {@link BSSLConnection_MakeBackend}. May be used to
+ *                  request performing SSL operations in threads.
+ * @param ssl_peer_cert if using SSL, the certificate we expect the peer to have
+ * @param ssl_peer_cert_len if using SSL, the length of the certificate
+ * @param payload_mtu maximum packet size as seen from the user. Must be >=0.
+ * @param sock_sndbuf socket SO_SNDBUF option. Specify <=0 to not set it.
+ * @param user_recv_if interface to use for submitting received packets. Its MTU
+ *                     must be >=payload_mtu.
+ * @param logfunc function which prepends the log prefix using {@link BLog_Append}
+ * @param handler_error handler function invoked when a connection error occurs
+ * @param user value to pass to handler functions
+ * @return 1 on success, 0 on failure
+ */
+int StreamPeerIO_Init (
+    StreamPeerIO *pio,
+    BReactor *reactor,
+    BThreadWorkDispatcher *twd,
+    int ssl,
+    int ssl_flags,
+    uint8_t *ssl_peer_cert,
+    int ssl_peer_cert_len,
+    int payload_mtu,
+    int sock_sndbuf,
+    PacketPassInterface *user_recv_if,
+    BLog_logfunc logfunc,
+    StreamPeerIO_handler_error handler_error,
+    void *user
+) WARN_UNUSED;
+
+/**
+ * Frees the object.
+ *
+ * @param pio the object
+ */
+void StreamPeerIO_Free (StreamPeerIO *pio);
+
+/**
+ * Returns the interface for sending packets to the peer.
+ * The OTP warning handler may be called from within Send calls
+ * to the interface.
+ *
+ * @param pio the object
+ * @return interface for sending packets to the peer
+ */
+PacketPassInterface * StreamPeerIO_GetSendInput (StreamPeerIO *pio);
+
+/**
+ * Starts an attempt to connect to the peer.
+ * On success, the object enters connecting state.
+ * On failure, the object enters default state.
+ *
+ * @param pio the object
+ * @param addr address to connect to
+ * @param password identification code to send to the peer
+ * @param ssl_cert if using SSL, the client certificate to use. This object does not
+ *                 take ownership of the certificate; it must remain valid until
+ *                 the object is reset.
+ * @param ssl_key if using SSL, the private key to use. This object does not take
+ *                ownership of the key; it must remain valid until the object is reset.
+ * @return 1 on success, 0 on failure
+ */
+int StreamPeerIO_Connect (StreamPeerIO *pio, BAddr addr, uint64_t password, CERTCertificate *ssl_cert, SECKEYPrivateKey *ssl_key) WARN_UNUSED;
+
+/**
+ * Starts an attempt to accept a connection from the peer.
+ * The object enters listening state.
+ *
+ * @param pio the object
+ * @param listener {@link PasswordListener} object to use for accepting a connection.
+ *                 The listener must have SSL enabled if and only if this object has
+ *                 SSL enabled. The listener must be available until the object is
+ *                 reset or {@link StreamPeerIO_handler_up} is called.
+ * @param password will return the identification code the peer should send when connecting
+ */
+void StreamPeerIO_Listen (StreamPeerIO *pio, PasswordListener *listener, uint64_t *password);
+
+#endif
diff --git a/external/badvpn_dns/client/badvpn-client.8 b/external/badvpn_dns/client/badvpn-client.8
new file mode 100644
index 0000000..4f41203
--- /dev/null
+++ b/external/badvpn_dns/client/badvpn-client.8
@@ -0,0 +1,316 @@
+.TH badvpn-client 8 "14 July 2011"
+.SH NAME
+badvpn-client \- VPN node daemon for the BadVPN peer-to-peer VPN system
+.SH SYNOPSIS
+.B badvpn-client
+.RS
+.RB "[" --help "]"
+.br
+.RB "[" --version "]"
+.br
+.RB "[" --logger " <stdout/syslog>]"
+.br
+(logger=syslog?
+.br
+.RS
+.br
+.RB "[" --syslog-facility " <string>]"
+.br
+.RB "[" --syslog-ident " <string>]"
+.br
+.RE
+)
+.br
+.RB "[" --loglevel " <0-5/none/error/warning/notice/info/debug>]"
+.br
+.RB "[" --channel-loglevel " <channel-name> <0-5/none/error/warning/notice/info/debug>] ..."
+.br
+.RB "[" --threads " <integer>]"
+.br
+.RB "[" --ssl " " --nssdb " <string> " --client-cert-name " <string>]"
+.br
+.RB "[" --server-name " <string>]"
+.br
+.BR --server-addr " <addr>"
+.br
+.RB "[" --tapdev " <name>]"
+.br
+.RB "[" --scope " <scope_name>] ..."
+.br
+[
+.br
+.RS
+.BR --bind-addr " <addr>"
+.br
+.RB "(transport-mode=udp? " --num-ports " <num>)"
+.br
+.RB "[" --ext-addr " <addr / {server_reported}:port> <scope_name>] ..."
+.br
+.RE
+] ...
+.br
+.BR --transport-mode " <udp/tcp>"
+.br
+(transport-mode=udp?
+.br
+.RS
+.BR --encryption-mode " <blowfish/aes/none>"
+.br
+.BR --hash-mode " <md5/sha1/none>"
+.br
+.RB "[" --otp " <blowfish/aes> <num> <num-warn>]"
+.br
+.RB "[" --fragmentation-latency " <milliseconds>]"
+.br
+.RE
+)
+.br
+(transport-mode=tcp?
+.br
+.RS
+.RB "(ssl? [" --peer-ssl "])"
+.br
+.RB "[" --peer-tcp-socket-sndbuf " <bytes / 0>]"
+.br
+.RE
+)
+.br
+.RB "[" --send-buffer-size " <num-packets>]"
+.br
+.RB "[" --send-buffer-relay-size " <num-packets>]"
+.br
+.RB "[" --max-macs " <num>]"
+.br
+.RB "[" --max-groups " <num>]"
+.br
+.RB "[" --igmp-group-membership-interval " <ms>]"
+.br
+.RB "[" --igmp-last-member-query-time " <ms>]"
+.br
+.RB "[" --allow-peer-talk-without-ssl "]"
+.br
+.RE
+.SH INTRODUCTION
+.P
+This page documents the BadVPN client, a daemon for a node in a BadVPN VPN network.
+For a general description of BadVPN, see
+.BR badvpn (7).
+.SH DESCRIPTION
+.P
+The BadVPN client is a daemon that runs on a VPN node. It opens the TAP device, connects to
+the server, then keeps running while attempting to establish data connection to peers and
+tranferring data between the TAP device and the peers. Once it initializes, the program only
+terminates if it loses connection to the server, or if a signal is received.
+.SH OPTIONS
+.P
+The BadVPN client is configured entirely from command line.
+.TP
+.BR --help
+Print version and command line syntax and exit.
+.TP
+.BR --version
+Print version and exit.
+.TP
+.BR --logger " <stdout/syslog>"
+Select where to log messages. Default is stdout. Syslog is not available on Windows.
+.TP
+.BR --syslog-facility " <string>"
+When logging to syslog, set the logging facility. The facility name must be in lower case.
+.TP
+.BR --syslog-ident " <string>"
+When logging to syslog, set the ident.
+.TP
+.BR --loglevel " <0-5/none/error/warning/notice/info/debug>"
+Set the default logging level.
+.TP
+.BR --channel-loglevel " <channel-name> <0-5/none/error/warning/notice/info/debug>"
+Set the logging level for a specific logging channel.
+.TP
+.BR --threads " <integer>"
+Hint for the number of additional threads to use for potentionally long computations (such as
+encryption and OTP generation). If zero (0) (default), additional threads will be disabled and all
+computations will be done in the event loop. If negative (<0), a guess will be made, possibly
+based on the number of CPUs. If positive (>0), the given number of threads will be used.
+.TP
+.BR --ssl
+Use TLS. Requires --nssdb and --server-cert-name.
+.TP
+.BR --nssdb " <string>"
+When using TLS, the NSS database to use. Probably something like sql:/some/folder.
+.TP
+.BR --client-cert-name " <string>"
+When using TLS, the name of the certificate to use. The certificate must be readily accessible.
+.TP
+.BR --server-name " <string>"
+Set the name of the server used for validating the server's certificate. The server name defaults
+to the the name in the server address (or a numeric address).
+.TP
+.BR --server-addr " <addr>"
+Set the address for the server to listen on. See below for address format.
+.TP
+.BR --tapdev " <name>"
+Set the TAP device to use. See below on how to configure the device. A TAP device is a virtual card
+in the operating system, but rather than receiving from and sending frames to a piece of hardware,
+a program (this one) opens it to read from and write frames into. If the VPN network is set up correctly,
+the TAP devices on the VPN nodes will act as if they were all connected into a network switch.
+.TP
+.BR --scope " <scope_name>"
+Add an address scope allowed for connecting to peers. May be specified multiple times to add multiple
+scopes. The order of the scopes is irrelevant. Note that it must actually be possible to connect
+to addresses in the given scope; when another peer binds for us to connect to, we choose the first
+external address whose scope we recognize, and do not attempt further external addresses, even if
+establishing the connection fails.
+.TP
+.BR --bind-addr " <addr>"
+Add an address to allow binding on. See below for address format. When attempting to bind in order
+for some peer to connect to us, the addresses will be tried in the order they are specified. If UDP
+data transport is being used, a --num-ports option must follow to specify how many continuous ports
+to allow binding to. For the address to be useful, one or more --ext-addr options must follow.
+Note that when two peers need to establish a data connection, it is arbitrary which one will attempt
+to bind first.
+.TP
+.BR --num-ports " <num>"
+When using UDP transport, set the number of continuous ports for a previously specified bind address.
+Must follow a previous --bind-addr option.
+.TP
+.BR --ext-addr " <addr / {server_reported}:port> <scope_name>"
+Add an external address for a previously specified bind address. Must follow a previous --bind-addr
+option. May be specified multiple times to add multiple external addresses. See below for address
+format. Additionally, the IP address part can be {server_reported} to use the IPv4 address as the
+server sees us. The external addresses are tried by the connecting peer in the order they are specified.
+Note that the connecting peer only attempts to connect to the first address whose scope it recognizes
+and does not try other addresses. This means that all addresses must work for be able to communicate.
+.TP
+.BR --transport-mode " <udp/tcp>"
+Sets the transport protocol for data connections. UDP is recommended and works best for most networks.
+TCP can be used instead if the underlying network has high packet loss which your virtual network
+cannot tolerate. Must match on all peers.
+.TP
+.BR --encryption-mode " <blowfish/aes/none>"
+When using UDP transport, sets the encryption mode. None means no encryption, other options mean
+a specific cipher. Note that encryption is only useful if clients use TLS to connect to the server.
+The encryption mode must match on all peers.
+.TP
+.BR --hash-mode " <md5/sha1/none>"
+When using UDP transport, sets the hashing mode. None means no hashes, other options mean a specific
+type of hash. Note that hashing is only useful if encryption is used as well. The hash mode must
+match on all peers.
+.TP
+.BR --otp " <blowfish/aes> <num> <num-warn>"
+When using UDP transport, enables one-time passwords. The first argument specifies a block cipher
+used to generate passwords from a seed. The second argument specifies how many passwords are
+generated from a single seed. The third argument specifies after how many passwords used up for
+sending packets an attempt is made to negotiate a new seed with the other peer. num must be >0,
+and num-warn must be >0 and <=num. The difference (num - num-warn) should be large enough to allow
+a new seed to be negotiated before the sender runs out of passwords. Negotiating a seed involves
+the sending peer sending it to the receiving peer via the server and the receiving peer confirming
+it via the server. Note that one-time passwords are only useful if clients use TLS to connect to the
+server. The OTP option must match on all peers, except for num-warn.
+.TP
+.BR --fragmentation-latency " <milliseconds>"
+When using UDP transport, sets the maximum latency to sacrifice in order to pack frames into data
+packets more efficiently. If it is >=0, a timer of that many milliseconds is used to wait for further
+frames to put into an incomplete packet since the first chunk of the packet was written. If it is
+<0, packets are sent out immediately. Defaults to 0, which is the recommended setting.
+.TP
+.BR --peer-ssl
+When using TCP transport, enables TLS for data connections. Requires using TLS for server connection.
+For this to work, the peers must trust each others' cerificates, and the cerificates must grant the
+TLS server usage context. This option must match on all peers.
+.TP
+.BR --peer-tcp-socket-sndbuf " <bytes / 0>"
+Sets the value of the SO_SNDBUF socket option for peer TCP sockets (zero to not set). Lower values
+will improve fairness when data from multiple sources (local and relaying) is being sent to a
+given peer, but may result in lower bandwidth if the network's bandwidth-delay product is too big.
+.TP
+.BR --send-buffer-size " <num-packets>"
+Sets the minimum size of the peers' send buffers for sending frames originating from this system, in
+number of packets.
+.TP
+.BR --send-buffer-relay-size " <num-packets>"
+Sets the minimum size of the peers' send buffers for relaying frames from other peers, in number of
+packets.
+.TP
+.BR --max-macs " <num>"
+Sets the maximum number of MAC addresses to remember for a peer. When the number is exceeded, the least
+recently used slot will be reused.
+.TP
+.BR --max-groups " <num>"
+Sets the maximum number of IGMP group memberships to remember for a peer. When the number is exceeded,
+the least recently used slot will be reused.
+.TP
+.BR --igmp-group-membership-interval " <ms>"
+Sets the Group Membership Interval parameter for IGMP snooping, in milliseconds.
+.TP
+.BR --igmp-last-member-query-time " <ms>"
+Sets the Last Member Query Time parameter for IGMP snooping, in milliseconds.
+.TP
+.BR --allow-peer-talk-without-ssl
+When SSL is enabled, the clients not only connect to the server using SSL, but also exchange messages through
+the server through another layer of SSL. This protects the messages from attacks on the server. Older versions
+of BadVPN (<1.999.109), however, do not support this. This option allows older and newer clients to
+interoperate by not using SSL if the other peer does not support it. It does however negate the security
+benefits of using SSL, since the (potentionally compromised) server can then order peers not to use SSL.
+.SH "EXIT CODE"
+.P
+If initialization fails, exits with code 1. Otherwise runs until termination is requested or server connection
+is broken and exits with code 1.
+.SH "ADDRESS FORMAT"
+.P
+Addresses have the form ipaddr:port, where ipaddr is either an IPv4 address (name or numeric), or an
+IPv6 address enclosed in brackets [] (name or numeric again).
+.SH "TAP DEVICE CONFIGURATION"
+.P
+To use this program, you first have to configure a TAP network device that will act as an endpoint for
+the virtual network. The configuration depends on your operating system.
+.P
+Note that the client program does not configure the TAP device in any way; it only reads and writes
+frames from/to it. You are responsible for configuring it (e.g. putting it up and setting its IP address).
+.P
+.B Linux
+.P
+You need to enable the kernel configuration option CONFIG_TUN. If you enabled it as a module, you may
+have to load it (`modprobe tun`) before you can create the device.
+.P
+Then you should create a persistent TAP device for the VPN client program to open. This can be done with
+either the
+.B tunctl
+or the
+.B openvpn
+program. The device will be associated with a user account that will have permission to use it, which should
+be the same user as the client program will run as (not root!). To create the device with tunctl, use `tunctl -u <user> -t tapN`,
+and to create it with openvpn, use `openvpn --mktun --user <user> --dev tapN`, where N is a number that identifies the
+TAP device.
+.P
+Once the TAP device is created, pass `--tapdev tapN` to the client program to make it use this device. Note that the
+device will not be preserved across a shutdown of the system; consult your OS documentaton if you want to automate
+the creation or configuration of the device.
+.P
+.B Windows
+.P
+Windows does not come with a TAP driver. The client program uses the TAP-Win32 driver, which is part of OpenVPN.
+You need to install the OpenVPN open source (!) version, and in the installer enable at least the
+`TAP Virtual Ethernet Adapter` and `Add Shortcuts to Start Menu` options.
+You can get the installer at
+.br
+<http://openvpn.net/index.php/open-source/downloads.html>.
+.P
+The OpenVPN installer automatically creates one TAP device on your system when it's run for the first time.
+To create another device, use `Programs -> OpenVPN -> Utilities -> Add a new TAP virtual ethernet adapter`.
+You may have to install OpenVPN once again to make this shortcut appear.
+.P
+Once you have a TAP device, you can configure it like a physical network card. You can recognize TAP devices
+by their `Device Name` field.
+.P
+To use the device, pass `--tapdev "<driver_name>:<interface_name>"` to the client program, where <driver_name> is the name of
+the TAP driver (tap0901 for OpenVPN 2.1 and 2.2) (case sensitive), and <interface_name> is the (human) name of the TAP
+network interface (e.g. `Local Area Connection 2`).
+.SH "EXAMPLES"
+.P
+For examples of using BadVPN, see
+.BR badvpn (7).
+.SH "SEE ALSO"
+.BR badvpn-server (8),
+.BR badvpn (7)
+.SH AUTHORS
+Ambroz Bizjak <ambrop7 at gmail.com>
diff --git a/external/badvpn_dns/client/client.c b/external/badvpn_dns/client/client.c
new file mode 100644
index 0000000..2729d6b
--- /dev/null
+++ b/external/badvpn_dns/client/client.c
@@ -0,0 +1,2997 @@
+/**
+ * @file client.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <protocol/msgproto.h>
+#include <protocol/addr.h>
+#include <protocol/dataproto.h>
+#include <misc/version.h>
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/byteorder.h>
+#include <misc/nsskey.h>
+#include <misc/loglevel.h>
+#include <misc/loggers_string.h>
+#include <misc/string_begins_with.h>
+#include <misc/open_standard_streams.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <security/BSecurity.h>
+#include <security/BRandom.h>
+#include <system/BSignal.h>
+#include <system/BTime.h>
+#include <system/BNetwork.h>
+#include <nspr_support/DummyPRFileDesc.h>
+#include <nspr_support/BSSLConnection.h>
+#include <server_connection/ServerConnection.h>
+#include <tuntap/BTap.h>
+#include <threadwork/BThreadWork.h>
+
+#ifndef BADVPN_USE_WINAPI
+#include <base/BLog_syslog.h>
+#endif
+
+#include <client/client.h>
+
+#include <generated/blog_channel_client.h>
+
+#define TRANSPORT_MODE_UDP 0
+#define TRANSPORT_MODE_TCP 1
+
+#define LOGGER_STDOUT 1
+#define LOGGER_SYSLOG 2
+
+// command-line options
+struct ext_addr_option  {
+    char *addr;
+    char *scope;
+};
+struct bind_addr_option {
+    char *addr;
+    int num_ports;
+    int num_ext_addrs;
+    struct ext_addr_option ext_addrs[MAX_EXT_ADDRS];
+};
+struct {
+    int help;
+    int version;
+    int logger;
+    #ifndef BADVPN_USE_WINAPI
+    char *logger_syslog_facility;
+    char *logger_syslog_ident;
+    #endif
+    int loglevel;
+    int loglevels[BLOG_NUM_CHANNELS];
+    int threads;
+    int use_threads_for_ssl_handshake;
+    int use_threads_for_ssl_data;
+    int ssl;
+    char *nssdb;
+    char *client_cert_name;
+    char *server_name;
+    char *server_addr;
+    char *tapdev;
+    int num_scopes;
+    char *scopes[MAX_SCOPES];
+    int num_bind_addrs;
+    struct bind_addr_option bind_addrs[MAX_BIND_ADDRS];
+    int transport_mode;
+    int encryption_mode;
+    int hash_mode;
+    int otp_mode;
+    int otp_num;
+    int otp_num_warn;
+    int fragmentation_latency;
+    int peer_ssl;
+    int peer_tcp_socket_sndbuf;
+    int send_buffer_size;
+    int send_buffer_relay_size;
+    int max_macs;
+    int max_groups;
+    int igmp_group_membership_interval;
+    int igmp_last_member_query_time;
+    int allow_peer_talk_without_ssl;
+    int max_peers;
+} options;
+
+// bind addresses
+struct ext_addr {
+    int server_reported_port;
+    BAddr addr; // if server_reported_port>=0, defined only after hello received
+    char scope[64];
+};
+struct bind_addr {
+    BAddr addr;
+    int num_ports;
+    int num_ext_addrs;
+    struct ext_addr ext_addrs[MAX_EXT_ADDRS];
+};
+int num_bind_addrs;
+struct bind_addr bind_addrs[MAX_BIND_ADDRS];
+
+// TCP listeners
+PasswordListener listeners[MAX_BIND_ADDRS];
+
+// SPProto parameters (UDP only)
+struct spproto_security_params sp_params;
+
+// server address we connect to
+BAddr server_addr;
+
+// server name to use for SSL
+char server_name[256];
+
+// reactor
+BReactor ss;
+
+// thread work dispatcher
+BThreadWorkDispatcher twd;
+
+// client certificate if using SSL
+CERTCertificate *client_cert;
+
+// client private key if using SSL
+SECKEYPrivateKey *client_key;
+
+// device
+BTap device;
+int device_mtu;
+
+// DataProtoSource for device input (reading)
+DataProtoSource device_dpsource;
+
+// DPReceiveDevice for device output (writing)
+DPReceiveDevice device_output_dprd;
+
+// data communication MTU
+int data_mtu;
+
+// peers list
+LinkedList1 peers;
+int num_peers;
+
+// frame decider
+FrameDecider frame_decider;
+
+// peers that can be user as relays
+LinkedList1 relays;
+
+// peers than need a relay
+LinkedList1 waiting_relay_peers;
+
+// server connection
+ServerConnection server;
+
+// my ID, defined only after server_ready
+peerid_t my_id;
+
+// fair queue for sending peer messages to the server
+PacketPassFairQueue server_queue;
+
+// whether server is ready
+int server_ready;
+
+// dying server flow
+struct server_flow *dying_server_flow;
+
+// stops event processing, causing the program to exit
+static void terminate (void);
+
+// prints program name and version to standard output
+static void print_help (const char *name);
+
+// prints program name and version to standard output
+static void print_version (void);
+
+// parses the command line
+static int parse_arguments (int argc, char *argv[]);
+
+// processes certain command line options
+static int process_arguments (void);
+
+static int ssl_flags (void);
+
+// handler for program termination request
+static void signal_handler (void *unused);
+
+// adds a new peer
+static void peer_add (peerid_t id, int flags, const uint8_t *cert, int cert_len);
+
+// removes a peer
+static void peer_remove (struct peer_data *peer, int exiting);
+
+// appends the peer log prefix to the logger
+static void peer_logfunc (struct peer_data *peer);
+
+// passes a message to the logger, prepending it info about the peer
+static void peer_log (struct peer_data *peer, int level, const char *fmt, ...);
+
+// see if we are the master relative to this peer
+static int peer_am_master (struct peer_data *peer);
+
+// frees PeerChat, disconnecting it from the server flow
+static void peer_free_chat (struct peer_data *peer);
+
+// initializes the link
+static int peer_init_link (struct peer_data *peer);
+
+// frees link resources
+static void peer_free_link (struct peer_data *peer);
+
+// frees link, relaying, waiting relaying
+static void peer_cleanup_connections (struct peer_data *peer);
+
+// registers the peer as a relay provider
+static void peer_enable_relay_provider (struct peer_data *peer);
+
+// unregisters the peer as a relay provider
+static void peer_disable_relay_provider (struct peer_data *peer);
+
+// install relaying for a peer
+static void peer_install_relaying (struct peer_data *peer, struct peer_data *relay);
+
+// uninstall relaying for a peer
+static void peer_free_relaying (struct peer_data *peer);
+
+// handle a peer that needs a relay
+static void peer_need_relay (struct peer_data *peer);
+
+// inserts the peer into the need relay list
+static void peer_register_need_relay (struct peer_data *peer);
+
+// removes the peer from the need relay list
+static void peer_unregister_need_relay (struct peer_data *peer);
+
+// handle a link setup failure
+static void peer_reset (struct peer_data *peer);
+
+// fees chat and sends resetpeer
+static void peer_resetpeer (struct peer_data *peer);
+
+// chat handlers
+static void peer_chat_handler_error (struct peer_data *peer);
+static void peer_chat_handler_message (struct peer_data *peer, uint8_t *data, int data_len);
+
+// handlers for different message types
+static void peer_msg_youconnect (struct peer_data *peer, uint8_t *data, int data_len);
+static void peer_msg_cannotconnect (struct peer_data *peer, uint8_t *data, int data_len);
+static void peer_msg_cannotbind (struct peer_data *peer, uint8_t *data, int data_len);
+static void peer_msg_seed (struct peer_data *peer, uint8_t *data, int data_len);
+static void peer_msg_confirmseed (struct peer_data *peer, uint8_t *data, int data_len);
+static void peer_msg_youretry (struct peer_data *peer, uint8_t *data, int data_len);
+
+// handler from DatagramPeerIO when we should generate a new OTP send seed
+static void peer_udp_pio_handler_seed_warning (struct peer_data *peer);
+
+// handler from DatagramPeerIO when a new OTP seed can be recognized once it was provided to it
+static void peer_udp_pio_handler_seed_ready (struct peer_data *peer);
+
+// handler from DatagramPeerIO when an error occurs on the connection
+static void peer_udp_pio_handler_error (struct peer_data *peer);
+
+// handler from StreamPeerIO when an error occurs on the connection
+static void peer_tcp_pio_handler_error (struct peer_data *peer);
+
+// peer retry timer handler. The timer is used only on the master side,
+// wither when we detect an error, or the peer reports an error.
+static void peer_reset_timer_handler (struct peer_data *peer);
+
+// start binding, according to the protocol
+static void peer_start_binding (struct peer_data *peer);
+
+// tries binding on one address, according to the protocol
+static void peer_bind (struct peer_data *peer);
+
+static void peer_bind_one_address (struct peer_data *peer, int addr_index, int *cont);
+
+static void peer_connect (struct peer_data *peer, BAddr addr, uint8_t *encryption_key, uint64_t password);
+
+static int peer_start_msg (struct peer_data *peer, void **data, int type, int len);
+
+static void peer_end_msg (struct peer_data *peer);
+
+// sends a message with no payload to the peer
+static void peer_send_simple (struct peer_data *peer, int msgid);
+
+static void peer_send_conectinfo (struct peer_data *peer, int addr_index, int port_adjust, uint8_t *enckey, uint64_t pass);
+
+static void peer_send_confirmseed (struct peer_data *peer, uint16_t seed_id);
+
+// handler for peer DataProto up state changes
+static void peer_dataproto_handler (struct peer_data *peer, int up);
+
+// looks for a peer with the given ID
+static struct peer_data * find_peer_by_id (peerid_t id);
+
+// device error handler
+static void device_error_handler (void *unused);
+
+// DataProtoSource handler for packets from the device
+static void device_dpsource_handler (void *unused, const uint8_t *frame, int frame_len);
+
+// assign relays to clients waiting for them
+static void assign_relays (void);
+
+// checks if the given address scope is known (i.e. we can connect to an address in it)
+static char * address_scope_known (uint8_t *name, int name_len);
+
+// handlers for server messages
+static void server_handler_error (void *user);
+static void server_handler_ready (void *user, peerid_t param_my_id, uint32_t ext_ip);
+static void server_handler_newclient (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len);
+static void server_handler_endclient (void *user, peerid_t peer_id);
+static void server_handler_message (void *user, peerid_t peer_id, uint8_t *data, int data_len);
+
+// jobs
+static void peer_job_send_seed (struct peer_data *peer);
+static void peer_job_init (struct peer_data *peer);
+
+// server flows
+static struct server_flow * server_flow_init (void);
+static void server_flow_free (struct server_flow *flow);
+static void server_flow_die (struct server_flow *flow);
+static void server_flow_qflow_handler_busy (struct server_flow *flow);
+static void server_flow_connect (struct server_flow *flow, PacketRecvInterface *input);
+static void server_flow_disconnect (struct server_flow *flow);
+
+int main (int argc, char *argv[])
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    // open standard streams
+    open_standard_streams();
+    
+    // parse command-line arguments
+    if (!parse_arguments(argc, argv)) {
+        fprintf(stderr, "Failed to parse arguments\n");
+        print_help(argv[0]);
+        goto fail0;
+    }
+    
+    // handle --help and --version
+    if (options.help) {
+        print_version();
+        print_help(argv[0]);
+        return 0;
+    }
+    if (options.version) {
+        print_version();
+        return 0;
+    }
+    
+    // initialize logger
+    switch (options.logger) {
+        case LOGGER_STDOUT:
+            BLog_InitStdout();
+            break;
+        #ifndef BADVPN_USE_WINAPI
+        case LOGGER_SYSLOG:
+            if (!BLog_InitSyslog(options.logger_syslog_ident, options.logger_syslog_facility)) {
+                fprintf(stderr, "Failed to initialize syslog logger\n");
+                goto fail0;
+            }
+            break;
+        #endif
+        default:
+            ASSERT(0);
+    }
+    
+    // configure logger channels
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        if (options.loglevels[i] >= 0) {
+            BLog_SetChannelLoglevel(i, options.loglevels[i]);
+        }
+        else if (options.loglevel >= 0) {
+            BLog_SetChannelLoglevel(i, options.loglevel);
+        }
+    }
+    
+    BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION);
+    
+    if (options.ssl) {
+        // init NSPR
+        PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+        
+        // register local NSPR file types
+        if (!DummyPRFileDesc_GlobalInit()) {
+            BLog(BLOG_ERROR, "DummyPRFileDesc_GlobalInit failed");
+            goto fail01;
+        }
+        if (!BSSLConnection_GlobalInit()) {
+            BLog(BLOG_ERROR, "BSSLConnection_GlobalInit failed");
+            goto fail01;
+        }
+        
+        // init NSS
+        if (NSS_Init(options.nssdb) != SECSuccess) {
+            BLog(BLOG_ERROR, "NSS_Init failed (%d)", (int)PR_GetError());
+            goto fail01;
+        }
+        
+        // set cipher policy
+        if (NSS_SetDomesticPolicy() != SECSuccess) {
+            BLog(BLOG_ERROR, "NSS_SetDomesticPolicy failed (%d)", (int)PR_GetError());
+            goto fail02;
+        }
+        
+        // init server cache
+        if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_ConfigServerSessionIDCache failed (%d)", (int)PR_GetError());
+            goto fail02;
+        }
+        
+        // open server certificate and private key
+        if (!open_nss_cert_and_key(options.client_cert_name, &client_cert, &client_key)) {
+            BLog(BLOG_ERROR, "Cannot open certificate and key");
+            goto fail03;
+        }
+    }
+    
+    // initialize network
+    if (!BNetwork_GlobalInit()) {
+        BLog(BLOG_ERROR, "BNetwork_GlobalInit failed");
+        goto fail1;
+    }
+    
+    // init time
+    BTime_Init();
+    
+    // process arguments
+    if (!process_arguments()) {
+        BLog(BLOG_ERROR, "Failed to process arguments");
+        goto fail1;
+    }
+    
+    // init reactor
+    if (!BReactor_Init(&ss)) {
+        BLog(BLOG_ERROR, "BReactor_Init failed");
+        goto fail1;
+    }
+    
+    // setup signal handler
+    if (!BSignal_Init(&ss, signal_handler, NULL)) {
+        BLog(BLOG_ERROR, "BSignal_Init failed");
+        goto fail2;
+    }
+    
+    // init thread work dispatcher
+    if (!BThreadWorkDispatcher_Init(&twd, &ss, options.threads)) {
+        BLog(BLOG_ERROR, "BThreadWorkDispatcher_Init failed");
+        goto fail3;
+    }
+    
+    // init BSecurity
+    if (BThreadWorkDispatcher_UsingThreads(&twd)) {
+        if (!BSecurity_GlobalInitThreadSafe()) {
+            BLog(BLOG_ERROR, "BSecurity_GlobalInitThreadSafe failed");
+            goto fail4;
+        }
+    }
+    
+    // init listeners
+    int num_listeners = 0;
+    if (options.transport_mode == TRANSPORT_MODE_TCP) {
+        while (num_listeners < num_bind_addrs) {
+            struct bind_addr *addr = &bind_addrs[num_listeners];
+            if (!PasswordListener_Init(&listeners[num_listeners], &ss, &twd, addr->addr, TCP_MAX_PASSWORD_LISTENER_CLIENTS, options.peer_ssl, ssl_flags(), client_cert, client_key)) {
+                BLog(BLOG_ERROR, "PasswordListener_Init failed");
+                goto fail8;
+            }
+            num_listeners++;
+        }
+    }
+    
+    // init device
+    if (!BTap_Init(&device, &ss, options.tapdev, device_error_handler, NULL, 0)) {
+        BLog(BLOG_ERROR, "BTap_Init failed");
+        goto fail8;
+    }
+    
+    // remember device MTU
+    device_mtu = BTap_GetMTU(&device);
+    
+    BLog(BLOG_INFO, "device MTU is %d", device_mtu);
+    
+    // calculate data MTU
+    if (device_mtu > INT_MAX - DATAPROTO_MAX_OVERHEAD) {
+        BLog(BLOG_ERROR, "Device MTU is too large");
+        goto fail9;
+    }
+    data_mtu = DATAPROTO_MAX_OVERHEAD + device_mtu;
+    
+    // init device input
+    if (!DataProtoSource_Init(&device_dpsource, BTap_GetOutput(&device), device_dpsource_handler, NULL, &ss)) {
+        BLog(BLOG_ERROR, "DataProtoSource_Init failed");
+        goto fail9;
+    }
+    
+    // init device output
+    if (!DPReceiveDevice_Init(&device_output_dprd, device_mtu, (DPReceiveDevice_output_func)BTap_Send, &device, &ss, options.send_buffer_relay_size, PEER_RELAY_FLOW_INACTIVITY_TIME)) {
+        BLog(BLOG_ERROR, "DPReceiveDevice_Init failed");
+        goto fail10;
+    }
+    
+    // init peers list
+    LinkedList1_Init(&peers);
+    num_peers = 0;
+    
+    // init frame decider
+    FrameDecider_Init(&frame_decider, options.max_macs, options.max_groups, options.igmp_group_membership_interval, options.igmp_last_member_query_time, &ss);
+    
+    // init relays list
+    LinkedList1_Init(&relays);
+    
+    // init need relay list
+    LinkedList1_Init(&waiting_relay_peers);
+    
+    // start connecting to server
+    if (!ServerConnection_Init(&server, &ss, &twd, server_addr, SC_KEEPALIVE_INTERVAL, SERVER_BUFFER_MIN_PACKETS, options.ssl, ssl_flags(), client_cert, client_key, server_name, NULL,
+                               server_handler_error, server_handler_ready, server_handler_newclient, server_handler_endclient, server_handler_message
+    )) {
+        BLog(BLOG_ERROR, "ServerConnection_Init failed");
+        goto fail11;
+    }
+    
+    // set server not ready
+    server_ready = 0;
+    
+    // set no dying flow
+    dying_server_flow = NULL;
+    
+    // enter event loop
+    BLog(BLOG_NOTICE, "entering event loop");
+    BReactor_Exec(&ss);
+    
+    if (server_ready) {
+        // allow freeing server queue flows
+        PacketPassFairQueue_PrepareFree(&server_queue);
+        
+        // make ServerConnection stop using buffers from peers before they are freed
+        ServerConnection_ReleaseBuffers(&server);
+    }
+    
+    // free peers
+    LinkedList1Node *node;
+    while (node = LinkedList1_GetFirst(&peers)) {
+        struct peer_data *peer = UPPER_OBJECT(node, struct peer_data, list_node);
+        peer_remove(peer, 1);
+    }
+    
+    // free dying server flow
+    if (dying_server_flow) {
+        server_flow_free(dying_server_flow);
+    }
+    
+    if (server_ready) {
+        PacketPassFairQueue_Free(&server_queue);
+    }
+    ServerConnection_Free(&server);
+fail11:
+    FrameDecider_Free(&frame_decider);
+    DPReceiveDevice_Free(&device_output_dprd);
+fail10:
+    DataProtoSource_Free(&device_dpsource);
+fail9:
+    BTap_Free(&device);
+fail8:
+    if (options.transport_mode == TRANSPORT_MODE_TCP) {
+        while (num_listeners-- > 0) {
+            PasswordListener_Free(&listeners[num_listeners]);
+        }
+    }
+    if (BThreadWorkDispatcher_UsingThreads(&twd)) {
+        BSecurity_GlobalFreeThreadSafe();
+    }
+fail4:
+    // NOTE: BThreadWorkDispatcher must be freed before NSPR and stuff
+    BThreadWorkDispatcher_Free(&twd);
+fail3:
+    BSignal_Finish();
+fail2:
+    BReactor_Free(&ss);
+fail1:
+    if (options.ssl) {
+        CERT_DestroyCertificate(client_cert);
+        SECKEY_DestroyPrivateKey(client_key);
+fail03:
+        ASSERT_FORCE(SSL_ShutdownServerSessionIDCache() == SECSuccess)
+fail02:
+        SSL_ClearSessionCache();
+        ASSERT_FORCE(NSS_Shutdown() == SECSuccess)
+fail01:
+        ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS)
+        PL_ArenaFinish();
+    }
+    BLog(BLOG_NOTICE, "exiting");
+    BLog_Free();
+fail0:
+    // finish objects
+    DebugObjectGlobal_Finish();
+    return 1;
+}
+
+void terminate (void)
+{
+    BLog(BLOG_NOTICE, "tearing down");
+    
+    // exit event loop
+    BReactor_Quit(&ss, 0);
+}
+
+void print_help (const char *name)
+{
+    printf(
+        "Usage:\n"
+        "    %s\n"
+        "        [--help]\n"
+        "        [--version]\n"
+        "        [--logger <"LOGGERS_STRING">]\n"
+        #ifndef BADVPN_USE_WINAPI
+        "        (logger=syslog?\n"
+        "            [--syslog-facility <string>]\n"
+        "            [--syslog-ident <string>]\n"
+        "        )\n"
+        #endif
+        "        [--loglevel <0-5/none/error/warning/notice/info/debug>]\n"
+        "        [--channel-loglevel <channel-name> <0-5/none/error/warning/notice/info/debug>] ...\n"
+        "        [--threads <integer>]\n"
+        "        [--use-threads-for-ssl-handshake]\n"
+        "        [--use-threads-for-ssl-data]\n"
+        "        [--ssl --nssdb <string> --client-cert-name <string>]\n"
+        "        [--server-name <string>]\n"
+        "        --server-addr <addr>\n"
+        "        [--tapdev <name>]\n"
+        "        [--scope <scope_name>] ...\n"
+        "        [\n"
+        "            --bind-addr <addr>\n"
+        "            (transport-mode=udp? --num-ports <num>)\n"
+        "            [--ext-addr <addr / {server_reported}:port> <scope_name>] ...\n"
+        "        ] ...\n"
+        "        --transport-mode <udp/tcp>\n"
+        "        (transport-mode=udp?\n"
+        "            --encryption-mode <blowfish/aes/none>\n"
+        "            --hash-mode <md5/sha1/none>\n"
+        "            [--otp <blowfish/aes> <num> <num-warn>]\n"
+        "            [--fragmentation-latency <milliseconds>]\n"
+        "        )\n"
+        "        (transport-mode=tcp?\n"
+        "            (ssl? [--peer-ssl])\n"
+        "            [--peer-tcp-socket-sndbuf <bytes / 0>]\n"
+        "        )\n"
+        "        [--send-buffer-size <num-packets>]\n"
+        "        [--send-buffer-relay-size <num-packets>]\n"
+        "        [--max-macs <num>]\n"
+        "        [--max-groups <num>]\n"
+        "        [--igmp-group-membership-interval <ms>]\n"
+        "        [--igmp-last-member-query-time <ms>]\n"
+        "        [--allow-peer-talk-without-ssl]\n"
+        "        [--max-peers <number>]\n"
+        "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n",
+        name
+    );
+}
+
+void print_version (void)
+{
+    printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n");
+}
+
+int parse_arguments (int argc, char *argv[])
+{
+    if (argc <= 0) {
+        return 0;
+    }
+    
+    options.help = 0;
+    options.version = 0;
+    options.logger = LOGGER_STDOUT;
+    #ifndef BADVPN_USE_WINAPI
+    options.logger_syslog_facility = "daemon";
+    options.logger_syslog_ident = argv[0];
+    #endif
+    options.loglevel = -1;
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        options.loglevels[i] = -1;
+    }
+    options.threads = 0;
+    options.use_threads_for_ssl_handshake = 0;
+    options.use_threads_for_ssl_data = 0;
+    options.ssl = 0;
+    options.nssdb = NULL;
+    options.client_cert_name = NULL;
+    options.server_name = NULL;
+    options.server_addr = NULL;
+    options.tapdev = NULL;
+    options.num_scopes = 0;
+    options.num_bind_addrs = 0;
+    options.transport_mode = -1;
+    options.encryption_mode = -1;
+    options.hash_mode = -1;
+    options.otp_mode = SPPROTO_OTP_MODE_NONE;
+    options.fragmentation_latency = PEER_DEFAULT_UDP_FRAGMENTATION_LATENCY;
+    options.peer_ssl = 0;
+    options.peer_tcp_socket_sndbuf = -1;
+    options.send_buffer_size = PEER_DEFAULT_SEND_BUFFER_SIZE;
+    options.send_buffer_relay_size = PEER_DEFAULT_SEND_BUFFER_RELAY_SIZE;
+    options.max_macs = PEER_DEFAULT_MAX_MACS;
+    options.max_groups = PEER_DEFAULT_MAX_GROUPS;
+    options.igmp_group_membership_interval = DEFAULT_IGMP_GROUP_MEMBERSHIP_INTERVAL;
+    options.igmp_last_member_query_time = DEFAULT_IGMP_LAST_MEMBER_QUERY_TIME;
+    options.allow_peer_talk_without_ssl = 0;
+    options.max_peers = DEFAULT_MAX_PEERS;
+    
+    int have_fragmentation_latency = 0;
+    
+    int i;
+    for (i = 1; i < argc; i++) {
+        char *arg = argv[i];
+        if (!strcmp(arg, "--help")) {
+            options.help = 1;
+        }
+        else if (!strcmp(arg, "--version")) {
+            options.version = 1;
+        }
+        else if (!strcmp(arg, "--logger")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            char *arg2 = argv[i + 1];
+            if (!strcmp(arg2, "stdout")) {
+                options.logger = LOGGER_STDOUT;
+            }
+            #ifndef BADVPN_USE_WINAPI
+            else if (!strcmp(arg2, "syslog")) {
+                options.logger = LOGGER_SYSLOG;
+            }
+            #endif
+            else {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        #ifndef BADVPN_USE_WINAPI
+        else if (!strcmp(arg, "--syslog-facility")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.logger_syslog_facility = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--syslog-ident")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.logger_syslog_ident = argv[i + 1];
+            i++;
+        }
+        #endif
+        else if (!strcmp(arg, "--loglevel")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--channel-loglevel")) {
+            if (2 >= argc - i) {
+                fprintf(stderr, "%s: requires two arguments\n", arg);
+                return 0;
+            }
+            int channel = BLogGlobal_GetChannelByName(argv[i + 1]);
+            if (channel < 0) {
+                fprintf(stderr, "%s: wrong channel argument\n", arg);
+                return 0;
+            }
+            int loglevel = parse_loglevel(argv[i + 2]);
+            if (loglevel < 0) {
+                fprintf(stderr, "%s: wrong loglevel argument\n", arg);
+                return 0;
+            }
+            options.loglevels[channel] = loglevel;
+            i += 2;
+        }
+        else if (!strcmp(arg, "--threads")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.threads = atoi(argv[i + 1]);
+            i++;
+        }
+        else if (!strcmp(arg, "--use-threads-for-ssl-handshake")) {
+            options.use_threads_for_ssl_handshake = 1;
+        }
+        else if (!strcmp(arg, "--use-threads-for-ssl-data")) {
+            options.use_threads_for_ssl_data = 1;
+        }
+        else if (!strcmp(arg, "--ssl")) {
+            options.ssl = 1;
+        }
+        else if (!strcmp(arg, "--nssdb")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.nssdb = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--client-cert-name")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.client_cert_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--server-name")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.server_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--server-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.server_addr = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--tapdev")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.tapdev = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--scope")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.num_scopes == MAX_SCOPES) {
+                fprintf(stderr, "%s: too many\n", arg);
+                return 0;
+            }
+            options.scopes[options.num_scopes] = argv[i + 1];
+            options.num_scopes++;
+            i++;
+        }
+        else if (!strcmp(arg, "--bind-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.num_bind_addrs == MAX_BIND_ADDRS) {
+                fprintf(stderr, "%s: too many\n", arg);
+                return 0;
+            }
+            struct bind_addr_option *addr = &options.bind_addrs[options.num_bind_addrs];
+            addr->addr = argv[i + 1];
+            addr->num_ports = -1;
+            addr->num_ext_addrs = 0;
+            options.num_bind_addrs++;
+            i++;
+        }
+        else if (!strcmp(arg, "--num-ports")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.num_bind_addrs == 0) {
+                fprintf(stderr, "%s: must folow --bind-addr\n", arg);
+                return 0;
+            }
+            struct bind_addr_option *addr = &options.bind_addrs[options.num_bind_addrs - 1];
+            if ((addr->num_ports = atoi(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--ext-addr")) {
+            if (2 >= argc - i) {
+                fprintf(stderr, "%s: requires two arguments\n", arg);
+                return 0;
+            }
+            if (options.num_bind_addrs == 0) {
+                fprintf(stderr, "%s: must folow --bind-addr\n", arg);
+                return 0;
+            }
+            struct bind_addr_option *addr = &options.bind_addrs[options.num_bind_addrs - 1];
+            if (addr->num_ext_addrs == MAX_EXT_ADDRS) {
+                fprintf(stderr, "%s: too many\n", arg);
+                return 0;
+            }
+            struct ext_addr_option *eaddr = &addr->ext_addrs[addr->num_ext_addrs];
+            eaddr->addr = argv[i + 1];
+            eaddr->scope = argv[i + 2];
+            addr->num_ext_addrs++;
+            i += 2;
+        }
+        else if (!strcmp(arg, "--transport-mode")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            char *arg2 = argv[i + 1];
+            if (!strcmp(arg2, "udp")) {
+                options.transport_mode = TRANSPORT_MODE_UDP;
+            }
+            else if (!strcmp(arg2, "tcp")) {
+                options.transport_mode = TRANSPORT_MODE_TCP;
+            }
+            else {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--encryption-mode")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            char *arg2 = argv[i + 1];
+            if (!strcmp(arg2, "none")) {
+                options.encryption_mode = SPPROTO_ENCRYPTION_MODE_NONE;
+            }
+            else if (!strcmp(arg2, "blowfish")) {
+                options.encryption_mode = BENCRYPTION_CIPHER_BLOWFISH;
+            }
+            else if (!strcmp(arg2, "aes")) {
+                options.encryption_mode = BENCRYPTION_CIPHER_AES;
+            }
+            else {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--hash-mode")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            char *arg2 = argv[i + 1];
+            if (!strcmp(arg2, "none")) {
+                options.hash_mode = SPPROTO_HASH_MODE_NONE;
+            }
+            else if (!strcmp(arg2, "md5")) {
+                options.hash_mode = BHASH_TYPE_MD5;
+            }
+            else if (!strcmp(arg2, "sha1")) {
+                options.hash_mode = BHASH_TYPE_SHA1;
+            }
+            else {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--otp")) {
+            if (3 >= argc - i) {
+                fprintf(stderr, "%s: requires three arguments\n", arg);
+                return 0;
+            }
+            char *otp_mode = argv[i + 1];
+            char *otp_num = argv[i + 2];
+            char *otp_num_warn = argv[i + 3];
+            if (!strcmp(otp_mode, "blowfish")) {
+                options.otp_mode = BENCRYPTION_CIPHER_BLOWFISH;
+            }
+            else if (!strcmp(otp_mode, "aes")) {
+                options.otp_mode = BENCRYPTION_CIPHER_AES;
+            }
+            else {
+                fprintf(stderr, "%s: wrong mode\n", arg);
+                return 0;
+            }
+            if ((options.otp_num = atoi(otp_num)) <= 0) {
+                fprintf(stderr, "%s: wrong num\n", arg);
+                return 0;
+            }
+            options.otp_num_warn = atoi(otp_num_warn);
+            if (options.otp_num_warn <= 0 || options.otp_num_warn > options.otp_num) {
+                fprintf(stderr, "%s: wrong num warn\n", arg);
+                return 0;
+            }
+            i += 3;
+        }
+        else if (!strcmp(arg, "--fragmentation-latency")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.fragmentation_latency = atoi(argv[i + 1]);
+            have_fragmentation_latency = 1;
+            i++;
+        }
+        else if (!strcmp(arg, "--peer-ssl")) {
+            options.peer_ssl = 1;
+        }
+        else if (!strcmp(arg, "--peer-tcp-socket-sndbuf")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.peer_tcp_socket_sndbuf = atoi(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--send-buffer-size")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.send_buffer_size = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--send-buffer-relay-size")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.send_buffer_relay_size = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--max-macs")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.max_macs = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--max-groups")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.max_groups = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--igmp-group-membership-interval")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.igmp_group_membership_interval = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--igmp-last-member-query-time")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.igmp_last_member_query_time = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--max-peers")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.max_peers = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--allow-peer-talk-without-ssl")) {
+            options.allow_peer_talk_without_ssl = 1;
+        }
+        else {
+            fprintf(stderr, "unknown option: %s\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    if (options.ssl != !!options.nssdb) {
+        fprintf(stderr, "False: --ssl <=> --nssdb\n");
+        return 0;
+    }
+    
+    if (options.ssl != !!options.client_cert_name) {
+        fprintf(stderr, "False: --ssl <=> --client-cert-name\n");
+        return 0;
+    }
+    
+    if (!options.server_addr) {
+        fprintf(stderr, "False: --server-addr\n");
+        return 0;
+    }
+    
+    if (options.transport_mode < 0) {
+        fprintf(stderr, "False: --transport-mode\n");
+        return 0;
+    }
+    
+    if ((options.transport_mode == TRANSPORT_MODE_UDP) != (options.encryption_mode >= 0)) {
+        fprintf(stderr, "False: UDP <=> --encryption-mode\n");
+        return 0;
+    }
+    
+    if ((options.transport_mode == TRANSPORT_MODE_UDP) != (options.hash_mode >= 0)) {
+        fprintf(stderr, "False: UDP <=> --hash-mode\n");
+        return 0;
+    }
+    
+    if (!(!(options.otp_mode != SPPROTO_OTP_MODE_NONE) || (options.transport_mode == TRANSPORT_MODE_UDP))) {
+        fprintf(stderr, "False: --otp => UDP\n");
+        return 0;
+    }
+    
+    if (!(!have_fragmentation_latency || (options.transport_mode == TRANSPORT_MODE_UDP))) {
+        fprintf(stderr, "False: --fragmentation-latency => UDP\n");
+        return 0;
+    }
+    
+    if (!(!options.peer_ssl || (options.ssl && options.transport_mode == TRANSPORT_MODE_TCP))) {
+        fprintf(stderr, "False: --peer-ssl => (--ssl && TCP)\n");
+        return 0;
+    }
+    
+    if (!(!(options.peer_tcp_socket_sndbuf >= 0) || options.transport_mode == TRANSPORT_MODE_TCP)) {
+        fprintf(stderr, "False: --peer-tcp-socket-sndbuf => TCP\n");
+        return 0;
+    }
+    
+    return 1;
+}
+
+int process_arguments (void)
+{
+    // resolve server address
+    ASSERT(options.server_addr)
+    if (!BAddr_Parse(&server_addr, options.server_addr, server_name, sizeof(server_name))) {
+        BLog(BLOG_ERROR, "server addr: BAddr_Parse failed");
+        return 0;
+    }
+    
+    // override server name if requested
+    if (options.server_name) {
+        if (strlen(options.server_name) >= sizeof(server_name)) {
+            BLog(BLOG_ERROR, "server name: too long");
+            return 0;
+        }
+        strcpy(server_name, options.server_name);
+    }
+    
+    // resolve bind addresses and external addresses
+    num_bind_addrs = 0;
+    for (int i = 0; i < options.num_bind_addrs; i++) {
+        struct bind_addr_option *addr = &options.bind_addrs[i];
+        struct bind_addr *out = &bind_addrs[num_bind_addrs];
+        
+        // read addr
+        if (!BAddr_Parse(&out->addr, addr->addr, NULL, 0)) {
+            BLog(BLOG_ERROR, "bind addr: BAddr_Parse failed");
+            return 0;
+        }
+        
+        // read num ports
+        if (options.transport_mode == TRANSPORT_MODE_UDP) {
+            if (addr->num_ports < 0) {
+                BLog(BLOG_ERROR, "bind addr: num ports missing");
+                return 0;
+            }
+            out->num_ports = addr->num_ports;
+        }
+        else if (addr->num_ports >= 0) {
+            BLog(BLOG_ERROR, "bind addr: num ports given, but not using UDP");
+            return 0;
+        }
+        
+        // read ext addrs
+        out->num_ext_addrs = 0;
+        for (int j = 0; j < addr->num_ext_addrs; j++) {
+            struct ext_addr_option *eaddr = &addr->ext_addrs[j];
+            struct ext_addr *eout = &out->ext_addrs[out->num_ext_addrs];
+            
+            // read addr
+            if (string_begins_with(eaddr->addr, "{server_reported}:")) {
+                char *colon = strstr(eaddr->addr, ":");
+                if ((eout->server_reported_port = atoi(colon + 1)) < 0) {
+                    BLog(BLOG_ERROR, "ext addr: wrong port");
+                    return 0;
+                }
+            } else {
+                eout->server_reported_port = -1;
+                if (!BAddr_Parse(&eout->addr, eaddr->addr, NULL, 0)) {
+                    BLog(BLOG_ERROR, "ext addr: BAddr_Parse failed");
+                    return 0;
+                }
+                if (!addr_supported(eout->addr)) {
+                    BLog(BLOG_ERROR, "ext addr: addr_supported failed");
+                    return 0;
+                }
+            }
+            
+            // read scope
+            if (strlen(eaddr->scope) >= sizeof(eout->scope)) {
+                BLog(BLOG_ERROR, "ext addr: too long");
+                return 0;
+            }
+            strcpy(eout->scope, eaddr->scope);
+            
+            out->num_ext_addrs++;
+        }
+        
+        num_bind_addrs++;
+    }
+    
+    // initialize SPProto parameters
+    if (options.transport_mode == TRANSPORT_MODE_UDP) {
+        sp_params.encryption_mode = options.encryption_mode;
+        sp_params.hash_mode = options.hash_mode;
+        sp_params.otp_mode = options.otp_mode;
+        if (options.otp_mode > 0) {
+            sp_params.otp_num = options.otp_num;
+        }
+    }
+    
+    return 1;
+}
+
+int ssl_flags (void)
+{
+    int flags = 0;
+    if (options.use_threads_for_ssl_handshake) {
+        flags |= BSSLCONNECTION_FLAG_THREADWORK_HANDSHAKE;
+    }
+    if (options.use_threads_for_ssl_data) {
+        flags |= BSSLCONNECTION_FLAG_THREADWORK_IO;
+    }
+    return flags;
+}
+
+void signal_handler (void *unused)
+{
+    BLog(BLOG_NOTICE, "termination requested");
+    
+    terminate();
+}
+
+void peer_add (peerid_t id, int flags, const uint8_t *cert, int cert_len)
+{
+    ASSERT(server_ready)
+    ASSERT(num_peers < options.max_peers)
+    ASSERT(!find_peer_by_id(id))
+    ASSERT(id != my_id)
+    ASSERT(cert_len >= 0)
+    ASSERT(cert_len <= SCID_NEWCLIENT_MAX_CERT_LEN)
+    
+    // allocate structure
+    struct peer_data *peer = (struct peer_data *)malloc(sizeof(*peer));
+    if (!peer) {
+        BLog(BLOG_ERROR, "peer %d: failed to allocate memory", (int)id);
+        goto fail0;
+    }
+    
+    // remember id
+    peer->id = id;
+    
+    // remember flags
+    peer->flags = flags;
+    
+    // set no common name
+    peer->common_name = NULL;
+    
+    if (options.ssl) {
+        // remember certificate
+        memcpy(peer->cert, cert, cert_len);
+        peer->cert_len = cert_len;
+        
+        // make sure that CERT_DecodeCertFromPackage will interpretet the input as raw DER and not base64,
+        // in which case following workaroud wouldn't help
+        if (!(cert_len > 0 && (cert[0] & 0x1f) == 0x10)) {
+            peer_log(peer, BLOG_ERROR, "certificate does not look like DER");
+            goto fail1;
+        }
+        
+        // copy the certificate and append it a good load of zero bytes,
+        // hopefully preventing the crappy CERT_DecodeCertFromPackage from crashing
+        // by reading past the of its input
+        uint8_t *certbuf = (uint8_t *)malloc(cert_len + 100);
+        if (!certbuf) {
+            peer_log(peer, BLOG_ERROR, "malloc failed");
+            goto fail1;
+        }
+        memcpy(certbuf, cert, cert_len);
+        memset(certbuf + cert_len, 0, 100);
+        
+        // decode certificate, so we can extract the common name
+        CERTCertificate *nsscert = CERT_DecodeCertFromPackage((char *)certbuf, cert_len);
+        if (!nsscert) {
+            peer_log(peer, BLOG_ERROR, "CERT_DecodeCertFromPackage failed (%d)", PORT_GetError());
+            free(certbuf);
+            goto fail1;
+        }
+        
+        free(certbuf);
+        
+        // remember common name
+        if (!(peer->common_name = CERT_GetCommonName(&nsscert->subject))) {
+            peer_log(peer, BLOG_ERROR, "CERT_GetCommonName failed");
+            CERT_DestroyCertificate(nsscert);
+            goto fail1;
+        }
+        
+        CERT_DestroyCertificate(nsscert);
+    }
+    
+    // init and set init job (must be before initing server flow so we can send)
+    BPending_Init(&peer->job_init, BReactor_PendingGroup(&ss), (BPending_handler)peer_job_init, peer);
+    BPending_Set(&peer->job_init);
+    
+    // init server flow
+    if (!(peer->server_flow = server_flow_init())) {
+        peer_log(peer, BLOG_ERROR, "server_flow_init failed");
+        goto fail2;
+    }
+    
+    if ((peer->flags & SCID_NEWCLIENT_FLAG_SSL) && !options.ssl) {
+        peer_log(peer, BLOG_ERROR, "peer requires talking with SSL, but we're not using SSL!?");
+        goto fail3;
+    }
+    
+    if (options.ssl && !(peer->flags & SCID_NEWCLIENT_FLAG_SSL) && !options.allow_peer_talk_without_ssl) {
+        peer_log(peer, BLOG_ERROR, "peer requires talking without SSL, but we don't allow that");
+        goto fail3;
+    }
+    
+    // choose chat SSL mode
+    int chat_ssl_mode = PEERCHAT_SSL_NONE;
+    if ((peer->flags & SCID_NEWCLIENT_FLAG_SSL)) {
+        chat_ssl_mode = (peer_am_master(peer) ? PEERCHAT_SSL_SERVER : PEERCHAT_SSL_CLIENT);
+    }
+    
+    // init chat
+    if (!PeerChat_Init(&peer->chat, peer->id, chat_ssl_mode, ssl_flags(), client_cert, client_key, peer->cert, peer->cert_len, BReactor_PendingGroup(&ss), &twd, peer,
+        (BLog_logfunc)peer_logfunc,
+        (PeerChat_handler_error)peer_chat_handler_error,
+        (PeerChat_handler_message)peer_chat_handler_message
+    )) {
+        peer_log(peer, BLOG_ERROR, "PeerChat_Init failed");
+        goto fail3;
+    }
+    
+    // set no message
+    peer->chat_send_msg_len = -1;
+    
+    // connect server flow to chat
+    server_flow_connect(peer->server_flow, PeerChat_GetSendOutput(&peer->chat));
+    
+    // set have chat
+    peer->have_chat = 1;
+    
+    // set have no resetpeer
+    peer->have_resetpeer = 0;
+    
+    // init local flow
+    if (!DataProtoFlow_Init(&peer->local_dpflow, &device_dpsource, my_id, peer->id, options.send_buffer_size, -1, NULL, NULL)) {
+        peer_log(peer, BLOG_ERROR, "DataProtoFlow_Init failed");
+        goto fail4;
+    }
+    
+    // init frame decider peer
+    if (!FrameDeciderPeer_Init(&peer->decider_peer, &frame_decider, peer, (BLog_logfunc)peer_logfunc)) {
+        peer_log(peer, BLOG_ERROR, "FrameDeciderPeer_Init failed");
+        goto fail5;
+    }
+    
+    // init receive peer
+    DPReceivePeer_Init(&peer->receive_peer, &device_output_dprd, peer->id, &peer->decider_peer, !!(peer->flags & SCID_NEWCLIENT_FLAG_RELAY_CLIENT));
+    
+    // have no link
+    peer->have_link = 0;
+    
+    // have no relaying
+    peer->relaying_peer = NULL;
+    
+    // not waiting for relay
+    peer->waiting_relay = 0;
+    
+    // init reset timer
+    BTimer_Init(&peer->reset_timer, PEER_RETRY_TIME, (BTimer_handler)peer_reset_timer_handler, peer);
+    
+    // is not relay server
+    peer->is_relay = 0;
+    
+    // init binding
+    peer->binding = 0;
+    
+    // add to peers list
+    LinkedList1_Append(&peers, &peer->list_node);
+    num_peers++;
+    
+    switch (chat_ssl_mode) {
+        case PEERCHAT_SSL_NONE:
+            peer_log(peer, BLOG_INFO, "initialized; talking to peer in plaintext mode");
+            break;
+        case PEERCHAT_SSL_CLIENT:
+            peer_log(peer, BLOG_INFO, "initialized; talking to peer in SSL client mode");
+            break;
+        case PEERCHAT_SSL_SERVER:
+            peer_log(peer, BLOG_INFO, "initialized; talking to peer in SSL server mode");
+            break;
+    }
+    
+    return;
+    
+fail5:
+    DataProtoFlow_Free(&peer->local_dpflow);
+fail4:
+    server_flow_disconnect(peer->server_flow);
+    PeerChat_Free(&peer->chat);
+fail3:
+    server_flow_free(peer->server_flow);
+fail2:
+    BPending_Free(&peer->job_init);
+    if (peer->common_name) {
+        PORT_Free(peer->common_name);
+    }
+fail1:
+    free(peer);
+fail0:
+    return;
+}
+
+void peer_remove (struct peer_data *peer, int exiting)
+{
+    peer_log(peer, BLOG_INFO, "removing");
+    
+    // cleanup connections
+    peer_cleanup_connections(peer);
+    
+    ASSERT(!peer->have_link)
+    ASSERT(!peer->relaying_peer)
+    ASSERT(!peer->waiting_relay)
+    ASSERT(!peer->is_relay)
+    
+    // remove from peers list
+    LinkedList1_Remove(&peers, &peer->list_node);
+    num_peers--;
+    
+    // free reset timer
+    BReactor_RemoveTimer(&ss, &peer->reset_timer);
+    
+    // free receive peer
+    DPReceivePeer_Free(&peer->receive_peer);
+    
+    // free frame decider
+    FrameDeciderPeer_Free(&peer->decider_peer);
+    
+    // free local flow
+    DataProtoFlow_Free(&peer->local_dpflow);
+    
+    // free chat
+    if (peer->have_chat) {
+        peer_free_chat(peer);
+    }
+    
+    // free resetpeer
+    if (peer->have_resetpeer) {
+        // disconnect resetpeer source from server flow
+        server_flow_disconnect(peer->server_flow);
+        
+        // free resetpeer source
+        SinglePacketSource_Free(&peer->resetpeer_source);
+    }
+    
+    // free/die server flow
+    if (exiting || !PacketPassFairQueueFlow_IsBusy(&peer->server_flow->qflow)) {
+        server_flow_free(peer->server_flow);
+    } else {
+        server_flow_die(peer->server_flow);
+    }
+    
+    // free jobs
+    BPending_Free(&peer->job_init);
+    
+    // free common name
+    if (peer->common_name) {
+        PORT_Free(peer->common_name);
+    }
+    
+    // free peer structure
+    free(peer);
+}
+
+void peer_logfunc (struct peer_data *peer)
+{
+    BLog_Append("peer %d", (int)peer->id);
+    if (peer->common_name) {
+        BLog_Append(" (%s)", peer->common_name);
+    }
+    BLog_Append(": ");
+}
+
+void peer_log (struct peer_data *peer, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_LogViaFuncVarArg((BLog_logfunc)peer_logfunc, peer, BLOG_CURRENT_CHANNEL, level, fmt, vl);
+    va_end(vl);
+}
+
+int peer_am_master (struct peer_data *peer)
+{
+    return (my_id > peer->id);
+}
+
+void peer_free_chat (struct peer_data *peer)
+{
+    ASSERT(peer->have_chat)
+    
+    // disconnect chat from server flow
+    server_flow_disconnect(peer->server_flow);
+    
+    // free chat
+    PeerChat_Free(&peer->chat);
+    
+    // set have no chat
+    peer->have_chat = 0;
+}
+
+int peer_init_link (struct peer_data *peer)
+{
+    ASSERT(!peer->have_link)
+    ASSERT(!peer->relaying_peer)
+    ASSERT(!peer->waiting_relay)
+    
+    ASSERT(!peer->is_relay)
+    
+    // init receive receiver
+    DPReceiveReceiver_Init(&peer->receive_receiver, &peer->receive_peer);
+    PacketPassInterface *recv_if = DPReceiveReceiver_GetInput(&peer->receive_receiver);
+    
+    // init transport-specific link objects
+    PacketPassInterface *link_if;
+    if (options.transport_mode == TRANSPORT_MODE_UDP) {
+        // init DatagramPeerIO
+        if (!DatagramPeerIO_Init(
+            &peer->pio.udp.pio, &ss, data_mtu, CLIENT_UDP_MTU, sp_params,
+            options.fragmentation_latency, PEER_UDP_ASSEMBLER_NUM_FRAMES, recv_if,
+            options.otp_num_warn, &twd, peer,
+            (BLog_logfunc)peer_logfunc,
+            (DatagramPeerIO_handler_error)peer_udp_pio_handler_error,
+            (DatagramPeerIO_handler_otp_warning)peer_udp_pio_handler_seed_warning,
+            (DatagramPeerIO_handler_otp_ready)peer_udp_pio_handler_seed_ready
+        )) {
+            peer_log(peer, BLOG_ERROR, "DatagramPeerIO_Init failed");
+            goto fail1;
+        }
+        
+        if (SPPROTO_HAVE_OTP(sp_params)) {
+            // init send seed state
+            peer->pio.udp.sendseed_nextid = 0;
+            peer->pio.udp.sendseed_sent = 0;
+            
+            // init send seed job
+            BPending_Init(&peer->pio.udp.job_send_seed, BReactor_PendingGroup(&ss), (BPending_handler)peer_job_send_seed, peer);
+        }
+        
+        link_if = DatagramPeerIO_GetSendInput(&peer->pio.udp.pio);
+    } else {
+        // init StreamPeerIO
+        if (!StreamPeerIO_Init(
+            &peer->pio.tcp.pio, &ss, &twd, options.peer_ssl, ssl_flags(),
+            (options.peer_ssl ? peer->cert : NULL),
+            (options.peer_ssl ? peer->cert_len : -1),
+            data_mtu,
+            (options.peer_tcp_socket_sndbuf >= 0 ? options.peer_tcp_socket_sndbuf : PEER_DEFAULT_TCP_SOCKET_SNDBUF),
+            recv_if,
+            (BLog_logfunc)peer_logfunc,
+            (StreamPeerIO_handler_error)peer_tcp_pio_handler_error, peer
+        )) {
+            peer_log(peer, BLOG_ERROR, "StreamPeerIO_Init failed");
+            goto fail1;
+        }
+        
+        link_if = StreamPeerIO_GetSendInput(&peer->pio.tcp.pio);
+    }
+    
+    // init sending
+    if (!DataProtoSink_Init(&peer->send_dp, &ss, link_if, PEER_KEEPALIVE_INTERVAL, PEER_KEEPALIVE_RECEIVE_TIMER, (DataProtoSink_handler)peer_dataproto_handler, peer)) {
+        peer_log(peer, BLOG_ERROR, "DataProto_Init failed");
+        goto fail2;
+    }
+    
+    // attach local flow to our DataProtoSink
+    DataProtoFlow_Attach(&peer->local_dpflow, &peer->send_dp);
+    
+    // attach receive peer to our DataProtoSink
+    DPReceivePeer_AttachSink(&peer->receive_peer, &peer->send_dp);
+    
+    // set have link
+    peer->have_link = 1;
+    
+    return 1;
+    
+fail2:
+    if (options.transport_mode == TRANSPORT_MODE_UDP) {
+        if (SPPROTO_HAVE_OTP(sp_params)) {
+            BPending_Free(&peer->pio.udp.job_send_seed);
+        }
+        DatagramPeerIO_Free(&peer->pio.udp.pio);
+    } else {
+        StreamPeerIO_Free(&peer->pio.tcp.pio);
+    }
+fail1:
+    DPReceiveReceiver_Free(&peer->receive_receiver);
+    return 0;
+}
+
+void peer_free_link (struct peer_data *peer)
+{
+    ASSERT(peer->have_link)
+    ASSERT(!peer->is_relay)
+    
+    ASSERT(!peer->relaying_peer)
+    ASSERT(!peer->waiting_relay)
+    
+    // detach receive peer from our DataProtoSink
+    DPReceivePeer_DetachSink(&peer->receive_peer);
+    
+    // detach local flow from our DataProtoSink
+    DataProtoFlow_Detach(&peer->local_dpflow);
+    
+    // free sending
+    DataProtoSink_Free(&peer->send_dp);
+    
+    // free transport-specific link objects
+    if (options.transport_mode == TRANSPORT_MODE_UDP) {
+        if (SPPROTO_HAVE_OTP(sp_params)) {
+            BPending_Free(&peer->pio.udp.job_send_seed);
+        }
+        DatagramPeerIO_Free(&peer->pio.udp.pio);
+    } else {
+        StreamPeerIO_Free(&peer->pio.tcp.pio);
+    }
+    
+    // free receive receiver
+    DPReceiveReceiver_Free(&peer->receive_receiver);
+    
+    // set have no link
+    peer->have_link = 0;
+}
+
+void peer_cleanup_connections (struct peer_data *peer)
+{
+    if (peer->have_link) {
+        if (peer->is_relay) {
+            peer_disable_relay_provider(peer);
+        }
+        peer_free_link(peer);
+    }
+    else if (peer->relaying_peer) {
+        peer_free_relaying(peer);
+    }
+    else if (peer->waiting_relay) {
+        peer_unregister_need_relay(peer);
+    }
+    
+    ASSERT(!peer->have_link)
+    ASSERT(!peer->relaying_peer)
+    ASSERT(!peer->waiting_relay)
+    ASSERT(!peer->is_relay)
+}
+
+void peer_enable_relay_provider (struct peer_data *peer)
+{
+    ASSERT(peer->have_link)
+    ASSERT(!peer->is_relay)
+    
+    ASSERT(!peer->relaying_peer)
+    ASSERT(!peer->waiting_relay)
+    
+    // add to relays list
+    LinkedList1_Append(&relays, &peer->relay_list_node);
+    
+    // init users list
+    LinkedList1_Init(&peer->relay_users);
+    
+    // set is relay
+    peer->is_relay = 1;
+    
+    // assign relays
+    assign_relays();
+}
+
+void peer_disable_relay_provider (struct peer_data *peer)
+{
+    ASSERT(peer->is_relay)
+    
+    ASSERT(peer->have_link)
+    ASSERT(!peer->relaying_peer)
+    ASSERT(!peer->waiting_relay)
+    
+    // disconnect relay users
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&peer->relay_users)) {
+        struct peer_data *relay_user = UPPER_OBJECT(list_node, struct peer_data, relaying_list_node);
+        ASSERT(relay_user->relaying_peer == peer)
+        
+        // disconnect relay user
+        peer_free_relaying(relay_user);
+        
+        // add it to need relay list
+        peer_register_need_relay(relay_user);
+    }
+    
+    // remove from relays list
+    LinkedList1_Remove(&relays, &peer->relay_list_node);
+    
+    // set is not relay
+    peer->is_relay = 0;
+    
+    // assign relays
+    assign_relays();
+}
+
+void peer_install_relaying (struct peer_data *peer, struct peer_data *relay)
+{
+    ASSERT(!peer->relaying_peer)
+    ASSERT(!peer->have_link)
+    ASSERT(!peer->waiting_relay)
+    ASSERT(relay->is_relay)
+    
+    ASSERT(!peer->is_relay)
+    ASSERT(relay->have_link)
+    
+    peer_log(peer, BLOG_INFO, "installing relaying through %d", (int)relay->id);
+    
+    // add to relay's users list
+    LinkedList1_Append(&relay->relay_users, &peer->relaying_list_node);
+    
+    // attach local flow to relay
+    DataProtoFlow_Attach(&peer->local_dpflow, &relay->send_dp);
+    
+    // set relaying
+    peer->relaying_peer = relay;
+}
+
+void peer_free_relaying (struct peer_data *peer)
+{
+    ASSERT(peer->relaying_peer)
+    
+    ASSERT(!peer->have_link)
+    ASSERT(!peer->waiting_relay)
+    
+    struct peer_data *relay = peer->relaying_peer;
+    ASSERT(relay->is_relay)
+    ASSERT(relay->have_link)
+    
+    peer_log(peer, BLOG_INFO, "uninstalling relaying through %d", (int)relay->id);
+    
+    // detach local flow from relay
+    DataProtoFlow_Detach(&peer->local_dpflow);
+    
+    // remove from relay's users list
+    LinkedList1_Remove(&relay->relay_users, &peer->relaying_list_node);
+    
+    // set not relaying
+    peer->relaying_peer = NULL;
+}
+
+void peer_need_relay (struct peer_data *peer)
+{
+    ASSERT(!peer->is_relay)
+    
+    if (peer->waiting_relay) {
+        // already waiting for relay, do nothing
+        return;
+    }
+    
+    if (peer->have_link) {
+        peer_free_link(peer);
+    }
+    else if (peer->relaying_peer) {
+        peer_free_relaying(peer);
+    }
+    
+    // register the peer as needing a relay
+    peer_register_need_relay(peer);
+    
+    // assign relays
+    assign_relays();
+}
+
+void peer_register_need_relay (struct peer_data *peer)
+{
+    ASSERT(!peer->waiting_relay)
+    ASSERT(!peer->have_link)
+    ASSERT(!peer->relaying_peer)
+    
+    ASSERT(!peer->is_relay)
+    
+    // add to need relay list
+    LinkedList1_Append(&waiting_relay_peers, &peer->waiting_relay_list_node);
+    
+    // set waiting relay
+    peer->waiting_relay = 1;
+}
+
+void peer_unregister_need_relay (struct peer_data *peer)
+{
+    ASSERT(peer->waiting_relay)
+    
+    ASSERT(!peer->have_link)
+    ASSERT(!peer->relaying_peer)
+    ASSERT(!peer->is_relay)
+    
+    // remove from need relay list
+    LinkedList1_Remove(&waiting_relay_peers, &peer->waiting_relay_list_node);
+    
+    // set not waiting relay
+    peer->waiting_relay = 0;
+}
+
+void peer_reset (struct peer_data *peer)
+{
+    peer_log(peer, BLOG_NOTICE, "resetting");
+    
+    // cleanup connections
+    peer_cleanup_connections(peer);
+    
+    if (peer_am_master(peer)) {
+        // if we're the master, schedule retry
+        BReactor_SetTimer(&ss, &peer->reset_timer);
+    } else {
+        // if we're the slave, report to master
+        peer_send_simple(peer, MSGID_YOURETRY);
+    }
+}
+
+void peer_resetpeer (struct peer_data *peer)
+{
+    ASSERT(peer->have_chat)
+    ASSERT(!peer->have_resetpeer)
+    
+    // free chat
+    peer_free_chat(peer);
+    
+    // build resetpeer packet
+    struct packetproto_header pp_header;
+    struct sc_header sc_header;
+    struct sc_client_resetpeer sc_resetpeer;
+    pp_header.len = htol16(sizeof(struct sc_header) + sizeof(struct sc_client_resetpeer));
+    sc_header.type = htol8(SCID_RESETPEER);
+    sc_resetpeer.clientid = htol16(peer->id);
+    memcpy(peer->resetpeer_packet, &pp_header, sizeof(pp_header));
+    memcpy(peer->resetpeer_packet + sizeof(pp_header), &sc_header, sizeof(sc_header));
+    memcpy(peer->resetpeer_packet + sizeof(pp_header) + sizeof(sc_header), &sc_resetpeer, sizeof(sc_resetpeer));
+    
+    // init resetpeer sourse
+    SinglePacketSource_Init(&peer->resetpeer_source, peer->resetpeer_packet, sizeof(peer->resetpeer_packet), BReactor_PendingGroup(&ss));
+    
+    // connect server flow to resetpeer source
+    server_flow_connect(peer->server_flow, SinglePacketSource_GetOutput(&peer->resetpeer_source));
+    
+    // set have resetpeer
+    peer->have_resetpeer = 1;
+}
+
+void peer_chat_handler_error (struct peer_data *peer)
+{
+    ASSERT(peer->have_chat)
+    ASSERT(!peer->have_resetpeer)
+    
+    peer_log(peer, BLOG_ERROR, "chat error, sending resetpeer");
+    
+    peer_resetpeer(peer);
+}
+
+void peer_chat_handler_message (struct peer_data *peer, uint8_t *data, int data_len)
+{
+    ASSERT(peer->have_chat)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_MSGLEN)
+    
+    // parse message
+    msgParser parser;
+    if (!msgParser_Init(&parser, data, data_len)) {
+        peer_log(peer, BLOG_NOTICE, "msg: failed to parse");
+        return;
+    }
+    
+    // read message
+    uint16_t type = 0; // to remove warning
+    ASSERT_EXECUTE(msgParser_Gettype(&parser, &type))
+    uint8_t *payload = NULL; // to remove warning
+    int payload_len = 0; // to remove warning
+    ASSERT_EXECUTE(msgParser_Getpayload(&parser, &payload, &payload_len))
+    
+    // dispatch according to message type
+    switch (type) {
+        case MSGID_YOUCONNECT:
+            peer_msg_youconnect(peer, payload, payload_len);
+            return;
+        case MSGID_CANNOTCONNECT:
+            peer_msg_cannotconnect(peer, payload, payload_len);
+            return;
+        case MSGID_CANNOTBIND:
+            peer_msg_cannotbind(peer, payload, payload_len);
+            return;
+        case MSGID_YOURETRY:
+            peer_msg_youretry(peer, payload, payload_len);
+            return;
+        case MSGID_SEED:
+            peer_msg_seed(peer, payload, payload_len);
+            return;
+        case MSGID_CONFIRMSEED:
+            peer_msg_confirmseed(peer, payload, payload_len);
+            return;
+        default:
+            BLog(BLOG_NOTICE, "msg: unknown type");
+            return;
+    }
+}
+
+void peer_msg_youconnect (struct peer_data *peer, uint8_t *data, int data_len)
+{
+    // init parser
+    msg_youconnectParser parser;
+    if (!msg_youconnectParser_Init(&parser, data, data_len)) {
+        peer_log(peer, BLOG_WARNING, "msg_youconnect: failed to parse");
+        return;
+    }
+    
+    // try addresses
+    BAddr addr;
+    while (1) {
+        // get address message
+        uint8_t *addrmsg_data;
+        int addrmsg_len;
+        if (!msg_youconnectParser_Getaddr(&parser, &addrmsg_data, &addrmsg_len)) {
+            peer_log(peer, BLOG_NOTICE, "msg_youconnect: no usable addresses");
+            peer_send_simple(peer, MSGID_CANNOTCONNECT);
+            return;
+        }
+        
+        // parse address message
+        msg_youconnect_addrParser aparser;
+        if (!msg_youconnect_addrParser_Init(&aparser, addrmsg_data, addrmsg_len)) {
+            peer_log(peer, BLOG_WARNING, "msg_youconnect: failed to parse address message");
+            return;
+        }
+        
+        // check if the address scope is known
+        uint8_t *name_data = NULL; // to remove warning
+        int name_len = 0; // to remove warning
+        ASSERT_EXECUTE(msg_youconnect_addrParser_Getname(&aparser, &name_data, &name_len))
+        char *name;
+        if (!(name = address_scope_known(name_data, name_len))) {
+            continue;
+        }
+        
+        // read address
+        uint8_t *addr_data = NULL; // to remove warning
+        int addr_len = 0; // to remove warning
+        ASSERT_EXECUTE(msg_youconnect_addrParser_Getaddr(&aparser, &addr_data, &addr_len))
+        if (!addr_read(addr_data, addr_len, &addr)) {
+            peer_log(peer, BLOG_WARNING, "msg_youconnect: failed to read address");
+            continue;
+        }
+        
+        peer_log(peer, BLOG_NOTICE, "msg_youconnect: using address in scope '%s'", name);
+        break;
+    }
+    
+    // discard further addresses
+    msg_youconnectParser_Forwardaddr(&parser);
+    
+    uint8_t *key = NULL;
+    uint64_t password = 0;
+    
+    // read additonal parameters
+    if (options.transport_mode == TRANSPORT_MODE_UDP) {
+        if (SPPROTO_HAVE_ENCRYPTION(sp_params)) {
+            int key_len;
+            if (!msg_youconnectParser_Getkey(&parser, &key, &key_len)) {
+                peer_log(peer, BLOG_WARNING, "msg_youconnect: no key");
+                return;
+            }
+            if (key_len != BEncryption_cipher_key_size(sp_params.encryption_mode)) {
+                peer_log(peer, BLOG_WARNING, "msg_youconnect: wrong key size");
+                return;
+            }
+        }
+    } else {
+        if (!msg_youconnectParser_Getpassword(&parser, &password)) {
+            peer_log(peer, BLOG_WARNING, "msg_youconnect: no password");
+            return;
+        }
+    }
+    
+    if (!msg_youconnectParser_GotEverything(&parser)) {
+        peer_log(peer, BLOG_WARNING, "msg_youconnect: stray data");
+        return;
+    }
+    
+    peer_log(peer, BLOG_INFO, "connecting");
+    
+    peer_connect(peer, addr, key, password);
+}
+
+void peer_msg_cannotconnect (struct peer_data *peer, uint8_t *data, int data_len)
+{
+    if (data_len != 0) {
+        peer_log(peer, BLOG_WARNING, "msg_cannotconnect: invalid length");
+        return;
+    }
+    
+    if (!peer->binding) {
+        peer_log(peer, BLOG_WARNING, "msg_cannotconnect: not binding");
+        return;
+    }
+    
+    peer_log(peer, BLOG_INFO, "peer could not connect");
+    
+    // continue trying bind addresses
+    peer_bind(peer);
+    return;
+}
+
+void peer_msg_cannotbind (struct peer_data *peer, uint8_t *data, int data_len)
+{
+    if (data_len != 0) {
+        peer_log(peer, BLOG_WARNING, "msg_cannotbind: invalid length");
+        return;
+    }
+    
+    peer_log(peer, BLOG_INFO, "peer cannot bind");
+    
+    if (!peer_am_master(peer)) {
+        peer_start_binding(peer);
+    } else {
+        if (!peer->is_relay) {
+            peer_need_relay(peer);
+        }
+    }
+}
+
+void peer_msg_seed (struct peer_data *peer, uint8_t *data, int data_len)
+{
+    msg_seedParser parser;
+    if (!msg_seedParser_Init(&parser, data, data_len)) {
+        peer_log(peer, BLOG_WARNING, "msg_seed: failed to parse");
+        return;
+    }
+    
+    // read message
+    uint16_t seed_id = 0; // to remove warning
+    ASSERT_EXECUTE(msg_seedParser_Getseed_id(&parser, &seed_id))
+    uint8_t *key = NULL; // to remove warning
+    int key_len = 0; // to remove warning
+    ASSERT_EXECUTE(msg_seedParser_Getkey(&parser, &key, &key_len))
+    uint8_t *iv = NULL; // to remove warning
+    int iv_len = 0; // to remove warning
+    ASSERT_EXECUTE(msg_seedParser_Getiv(&parser, &iv, &iv_len))
+    
+    if (options.transport_mode != TRANSPORT_MODE_UDP) {
+        peer_log(peer, BLOG_WARNING, "msg_seed: not in UDP mode");
+        return;
+    }
+    
+    if (!SPPROTO_HAVE_OTP(sp_params)) {
+        peer_log(peer, BLOG_WARNING, "msg_seed: OTPs disabled");
+        return;
+    }
+    
+    if (key_len != BEncryption_cipher_key_size(sp_params.otp_mode)) {
+        peer_log(peer, BLOG_WARNING, "msg_seed: wrong key length");
+        return;
+    }
+    
+    if (iv_len != BEncryption_cipher_block_size(sp_params.otp_mode)) {
+        peer_log(peer, BLOG_WARNING, "msg_seed: wrong IV length");
+        return;
+    }
+    
+    if (!peer->have_link) {
+        peer_log(peer, BLOG_WARNING, "msg_seed: have no link");
+        return;
+    }
+    
+    peer_log(peer, BLOG_DEBUG, "received OTP receive seed");
+    
+    // add receive seed
+    DatagramPeerIO_AddOTPRecvSeed(&peer->pio.udp.pio, seed_id, key, iv);
+    
+    // remember seed ID so we can confirm it from peer_udp_pio_handler_seed_ready
+    peer->pio.udp.pending_recvseed_id = seed_id;
+}
+
+void peer_msg_confirmseed (struct peer_data *peer, uint8_t *data, int data_len)
+{
+    msg_confirmseedParser parser;
+    if (!msg_confirmseedParser_Init(&parser, data, data_len)) {
+        peer_log(peer, BLOG_WARNING, "msg_confirmseed: failed to parse");
+        return;
+    }
+    
+    // read message
+    uint16_t seed_id = 0; // to remove warning
+    ASSERT_EXECUTE(msg_confirmseedParser_Getseed_id(&parser, &seed_id))
+    
+    if (options.transport_mode != TRANSPORT_MODE_UDP) {
+        peer_log(peer, BLOG_WARNING, "msg_confirmseed: not in UDP mode");
+        return;
+    }
+    
+    if (!SPPROTO_HAVE_OTP(sp_params)) {
+        peer_log(peer, BLOG_WARNING, "msg_confirmseed: OTPs disabled");
+        return;
+    }
+    
+    if (!peer->have_link) {
+        peer_log(peer, BLOG_WARNING, "msg_confirmseed: have no link");
+        return;
+    }
+    
+    if (!peer->pio.udp.sendseed_sent) {
+        peer_log(peer, BLOG_WARNING, "msg_confirmseed: no seed has been sent");
+        return;
+    }
+    
+    if (seed_id != peer->pio.udp.sendseed_sent_id) {
+        peer_log(peer, BLOG_WARNING, "msg_confirmseed: invalid seed: expecting %d, received %d", (int)peer->pio.udp.sendseed_sent_id, (int)seed_id);
+        return;
+    }
+    
+    peer_log(peer, BLOG_DEBUG, "OTP send seed confirmed");
+    
+    // no longer waiting for confirmation
+    peer->pio.udp.sendseed_sent = 0;
+    
+    // start using the seed
+    DatagramPeerIO_SetOTPSendSeed(&peer->pio.udp.pio, peer->pio.udp.sendseed_sent_id, peer->pio.udp.sendseed_sent_key, peer->pio.udp.sendseed_sent_iv);
+}
+
+void peer_msg_youretry (struct peer_data *peer, uint8_t *data, int data_len)
+{
+    if (data_len != 0) {
+        peer_log(peer, BLOG_WARNING, "msg_youretry: invalid length");
+        return;
+    }
+    
+    if (!peer_am_master(peer)) {
+        peer_log(peer, BLOG_WARNING, "msg_youretry: we are not master");
+        return;
+    }
+    
+    peer_log(peer, BLOG_NOTICE, "requests reset");
+    
+    peer_reset(peer);
+}
+
+void peer_udp_pio_handler_seed_warning (struct peer_data *peer)
+{
+    ASSERT(options.transport_mode == TRANSPORT_MODE_UDP)
+    ASSERT(SPPROTO_HAVE_OTP(sp_params))
+    ASSERT(peer->have_link)
+    
+    // generate and send a new seed
+    if (!peer->pio.udp.sendseed_sent) {
+        BPending_Set(&peer->pio.udp.job_send_seed);
+    }
+}
+
+void peer_udp_pio_handler_seed_ready (struct peer_data *peer)
+{
+    ASSERT(options.transport_mode == TRANSPORT_MODE_UDP)
+    ASSERT(SPPROTO_HAVE_OTP(sp_params))
+    ASSERT(peer->have_link)
+    
+    // send confirmation
+    peer_send_confirmseed(peer, peer->pio.udp.pending_recvseed_id);
+}
+
+void peer_udp_pio_handler_error (struct peer_data *peer)
+{
+    ASSERT(options.transport_mode == TRANSPORT_MODE_UDP)
+    ASSERT(peer->have_link)
+    
+    peer_log(peer, BLOG_NOTICE, "UDP connection failed");
+    
+    peer_reset(peer);
+    return;
+}
+
+void peer_tcp_pio_handler_error (struct peer_data *peer)
+{
+    ASSERT(options.transport_mode == TRANSPORT_MODE_TCP)
+    ASSERT(peer->have_link)
+    
+    peer_log(peer, BLOG_NOTICE, "TCP connection failed");
+    
+    peer_reset(peer);
+    return;
+}
+
+void peer_reset_timer_handler (struct peer_data *peer)
+{
+    ASSERT(peer_am_master(peer))
+    
+    BLog(BLOG_NOTICE, "retry timer expired");
+    
+    // start setup process
+    peer_start_binding(peer);
+}
+
+void peer_start_binding (struct peer_data *peer)
+{
+    peer->binding = 1;
+    peer->binding_addrpos = 0;
+    
+    peer_bind(peer);
+}
+
+void peer_bind (struct peer_data *peer)
+{
+    ASSERT(peer->binding)
+    ASSERT(peer->binding_addrpos >= 0)
+    ASSERT(peer->binding_addrpos <= num_bind_addrs)
+    
+    while (peer->binding_addrpos < num_bind_addrs) {
+        // if there are no external addresses, skip bind address
+        if (bind_addrs[peer->binding_addrpos].num_ext_addrs == 0) {
+            peer->binding_addrpos++;
+            continue;
+        }
+        
+        // try to bind
+        int cont;
+        peer_bind_one_address(peer, peer->binding_addrpos, &cont);
+        
+        // increment address counter
+        peer->binding_addrpos++;
+        
+        if (!cont) {
+            return;
+        }
+    }
+    
+    peer_log(peer, BLOG_NOTICE, "no more addresses to bind to");
+    
+    // no longer binding
+    peer->binding = 0;
+    
+    // tell the peer we failed to bind
+    peer_send_simple(peer, MSGID_CANNOTBIND);
+    
+    // if we are the slave, setup relaying
+    if (!peer_am_master(peer)) {
+        if (!peer->is_relay) {
+            peer_need_relay(peer);
+        }
+    }
+}
+
+void peer_bind_one_address (struct peer_data *peer, int addr_index, int *cont)
+{
+    ASSERT(addr_index >= 0)
+    ASSERT(addr_index < num_bind_addrs)
+    ASSERT(bind_addrs[addr_index].num_ext_addrs > 0)
+    
+    // get a fresh link
+    peer_cleanup_connections(peer);
+    if (!peer_init_link(peer)) {
+        peer_log(peer, BLOG_ERROR, "cannot get link");
+        *cont = 0;
+        peer_reset(peer);
+        return;
+    }
+    
+    if (options.transport_mode == TRANSPORT_MODE_UDP) {
+        // get addr
+        struct bind_addr *addr = &bind_addrs[addr_index];
+        
+        // try binding to all ports in the range
+        int port_add;
+        for (port_add = 0; port_add < addr->num_ports; port_add++) {
+            BAddr tryaddr = addr->addr;
+            BAddr_SetPort(&tryaddr, hton16(ntoh16(BAddr_GetPort(&tryaddr)) + port_add));
+            if (DatagramPeerIO_Bind(&peer->pio.udp.pio, tryaddr)) {
+                break;
+            }
+        }
+        if (port_add == addr->num_ports) {
+            BLog(BLOG_NOTICE, "failed to bind to any port");
+            *cont = 1;
+            return;
+        }
+        
+        uint8_t key[BENCRYPTION_MAX_KEY_SIZE];
+        
+        // generate and set encryption key
+        if (SPPROTO_HAVE_ENCRYPTION(sp_params)) {
+            BRandom_randomize(key, BEncryption_cipher_key_size(sp_params.encryption_mode));
+            DatagramPeerIO_SetEncryptionKey(&peer->pio.udp.pio, key);
+        }
+        
+        // schedule sending OTP seed
+        if (SPPROTO_HAVE_OTP(sp_params)) {
+            BPending_Set(&peer->pio.udp.job_send_seed);
+        }
+        
+        // send connectinfo
+        peer_send_conectinfo(peer, addr_index, port_add, key, 0);
+    } else {
+        // order StreamPeerIO to listen
+        uint64_t pass;
+        StreamPeerIO_Listen(&peer->pio.tcp.pio, &listeners[addr_index], &pass);
+        
+        // send connectinfo
+        peer_send_conectinfo(peer, addr_index, 0, NULL, pass);
+    }
+    
+    peer_log(peer, BLOG_NOTICE, "bound to address number %d", addr_index);
+    
+    *cont = 0;
+}
+
+void peer_connect (struct peer_data *peer, BAddr addr, uint8_t* encryption_key, uint64_t password)
+{
+    // get a fresh link
+    peer_cleanup_connections(peer);
+    if (!peer_init_link(peer)) {
+        peer_log(peer, BLOG_ERROR, "cannot get link");
+        peer_reset(peer);
+        return;
+    }
+    
+    if (options.transport_mode == TRANSPORT_MODE_UDP) {
+        // order DatagramPeerIO to connect
+        if (!DatagramPeerIO_Connect(&peer->pio.udp.pio, addr)) {
+            peer_log(peer, BLOG_NOTICE, "DatagramPeerIO_Connect failed");
+            peer_reset(peer);
+            return;
+        }
+        
+        // set encryption key
+        if (SPPROTO_HAVE_ENCRYPTION(sp_params)) {
+            DatagramPeerIO_SetEncryptionKey(&peer->pio.udp.pio, encryption_key);
+        }
+        
+        // generate and send a send seed
+        if (SPPROTO_HAVE_OTP(sp_params)) {
+            BPending_Set(&peer->pio.udp.job_send_seed);
+        }
+    } else {
+        // order StreamPeerIO to connect
+        if (!StreamPeerIO_Connect(&peer->pio.tcp.pio, addr, password, client_cert, client_key)) {
+            peer_log(peer, BLOG_NOTICE, "StreamPeerIO_Connect failed");
+            peer_reset(peer);
+            return;
+        }
+    }
+}
+
+static int peer_start_msg (struct peer_data *peer, void **data, int type, int len)
+{
+    ASSERT(len >= 0)
+    ASSERT(len <= MSG_MAX_PAYLOAD)
+    ASSERT(!(len > 0) || data)
+    ASSERT(peer->chat_send_msg_len == -1)
+    
+    // make sure we have chat
+    if (!peer->have_chat) {
+        peer_log(peer, BLOG_ERROR, "cannot send message, chat is down");
+        return 0;
+    }
+    
+#ifdef SIMULATE_PEER_OUT_OF_BUFFER
+    uint8_t x;
+    BRandom_randomize(&x, sizeof(x));
+    if (x < SIMULATE_PEER_OUT_OF_BUFFER) {
+        peer_log(peer, BLOG_ERROR, "simulating out of buffer, sending resetpeer");
+        peer_resetpeer(peer);
+        return 0;
+    }
+#endif
+    
+    // obtain buffer location
+    uint8_t *packet;
+    if (!PeerChat_StartMessage(&peer->chat, &packet)) {
+        peer_log(peer, BLOG_ERROR, "cannot send message, out of buffer, sending resetpeer");
+        peer_resetpeer(peer);
+        return 0;
+    }
+    
+    // write fields
+    msgWriter writer;
+    msgWriter_Init(&writer, packet);
+    msgWriter_Addtype(&writer, type);
+    uint8_t *payload_dst = msgWriter_Addpayload(&writer, len);
+    msgWriter_Finish(&writer);
+    
+    // set have message
+    peer->chat_send_msg_len = len;
+    
+    if (data) {
+        *data = payload_dst;
+    }
+    return 1;
+}
+
+static void peer_end_msg (struct peer_data *peer)
+{
+    ASSERT(peer->chat_send_msg_len >= 0)
+    ASSERT(peer->have_chat)
+    
+    // submit packet to buffer
+    PeerChat_EndMessage(&peer->chat, msg_SIZEtype + msg_SIZEpayload(peer->chat_send_msg_len));
+    
+    // set no message
+    peer->chat_send_msg_len = -1;
+}
+
+void peer_send_simple (struct peer_data *peer, int msgid)
+{
+    if (!peer_start_msg(peer, NULL, msgid, 0)) {
+        return;
+    }
+    peer_end_msg(peer);
+}
+
+void peer_send_conectinfo (struct peer_data *peer, int addr_index, int port_adjust, uint8_t *enckey, uint64_t pass)
+{
+    ASSERT(addr_index >= 0)
+    ASSERT(addr_index < num_bind_addrs)
+    ASSERT(bind_addrs[addr_index].num_ext_addrs > 0)
+    
+    // get address
+    struct bind_addr *bind_addr = &bind_addrs[addr_index];
+    
+    // remember encryption key size
+    int key_size = 0; // to remove warning
+    if (options.transport_mode == TRANSPORT_MODE_UDP && SPPROTO_HAVE_ENCRYPTION(sp_params)) {
+        key_size = BEncryption_cipher_key_size(sp_params.encryption_mode);
+    }
+    
+    // calculate message length ..
+    int msg_len = 0;
+    
+    // addresses
+    for (int i = 0; i < bind_addr->num_ext_addrs; i++) {
+        int addrmsg_len =
+            msg_youconnect_addr_SIZEname(strlen(bind_addr->ext_addrs[i].scope)) +
+            msg_youconnect_addr_SIZEaddr(addr_size(bind_addr->ext_addrs[i].addr));
+        msg_len += msg_youconnect_SIZEaddr(addrmsg_len);
+    }
+    
+    // encryption key
+    if (options.transport_mode == TRANSPORT_MODE_UDP && SPPROTO_HAVE_ENCRYPTION(sp_params)) {
+        msg_len += msg_youconnect_SIZEkey(key_size);
+    }
+    
+    // password
+    if (options.transport_mode == TRANSPORT_MODE_TCP) {
+        msg_len += msg_youconnect_SIZEpassword;
+    }
+    
+    // check if it's too big (because of the addresses)
+    if (msg_len > MSG_MAX_PAYLOAD) {
+        BLog(BLOG_ERROR, "cannot send too big youconnect message");
+        return;
+    }
+        
+    // start message
+    uint8_t *msg;
+    if (!peer_start_msg(peer, (void **)&msg, MSGID_YOUCONNECT, msg_len)) {
+        return;
+    }
+        
+    // init writer
+    msg_youconnectWriter writer;
+    msg_youconnectWriter_Init(&writer, msg);
+        
+    // write addresses
+    for (int i = 0; i < bind_addr->num_ext_addrs; i++) {
+        int name_len = strlen(bind_addr->ext_addrs[i].scope);
+        int addr_len = addr_size(bind_addr->ext_addrs[i].addr);
+        
+        // get a pointer for writing the address
+        int addrmsg_len =
+            msg_youconnect_addr_SIZEname(name_len) +
+            msg_youconnect_addr_SIZEaddr(addr_len);
+        uint8_t *addrmsg_dst = msg_youconnectWriter_Addaddr(&writer, addrmsg_len);
+        
+        // init address writer
+        msg_youconnect_addrWriter awriter;
+        msg_youconnect_addrWriter_Init(&awriter, addrmsg_dst);
+        
+        // write scope
+        uint8_t *name_dst = msg_youconnect_addrWriter_Addname(&awriter, name_len);
+        memcpy(name_dst, bind_addr->ext_addrs[i].scope, name_len);
+        
+        // write address with adjusted port
+        BAddr addr = bind_addr->ext_addrs[i].addr;
+        BAddr_SetPort(&addr, hton16(ntoh16(BAddr_GetPort(&addr)) + port_adjust));
+        uint8_t *addr_dst = msg_youconnect_addrWriter_Addaddr(&awriter, addr_len);
+        addr_write(addr_dst, addr);
+        
+        // finish address writer
+        msg_youconnect_addrWriter_Finish(&awriter);
+    }
+    
+    // write encryption key
+    if (options.transport_mode == TRANSPORT_MODE_UDP && SPPROTO_HAVE_ENCRYPTION(sp_params)) {
+        uint8_t *key_dst = msg_youconnectWriter_Addkey(&writer, key_size);
+        memcpy(key_dst, enckey, key_size);
+    }
+    
+    // write password
+    if (options.transport_mode == TRANSPORT_MODE_TCP) {
+        msg_youconnectWriter_Addpassword(&writer, pass);
+    }
+    
+    // finish writer
+    msg_youconnectWriter_Finish(&writer);
+    
+    // end message
+    peer_end_msg(peer);
+}
+
+void peer_send_confirmseed (struct peer_data *peer, uint16_t seed_id)
+{
+    ASSERT(options.transport_mode == TRANSPORT_MODE_UDP)
+    ASSERT(SPPROTO_HAVE_OTP(sp_params))
+    
+    // send confirmation
+    int msg_len = msg_confirmseed_SIZEseed_id;
+    uint8_t *msg;
+    if (!peer_start_msg(peer, (void **)&msg, MSGID_CONFIRMSEED, msg_len)) {
+        return;
+    }
+    msg_confirmseedWriter writer;
+    msg_confirmseedWriter_Init(&writer, msg);
+    msg_confirmseedWriter_Addseed_id(&writer, seed_id);
+    msg_confirmseedWriter_Finish(&writer);
+    peer_end_msg(peer);
+}
+
+void peer_dataproto_handler (struct peer_data *peer, int up)
+{
+    ASSERT(peer->have_link)
+    
+    if (up) {
+        peer_log(peer, BLOG_INFO, "up");
+        
+        // if it can be a relay provided, enable it
+        if ((peer->flags & SCID_NEWCLIENT_FLAG_RELAY_SERVER) && !peer->is_relay) {
+            peer_enable_relay_provider(peer);
+        }
+    } else {
+        peer_log(peer, BLOG_INFO, "down");
+        
+        // if it is a relay provider, disable it
+        if (peer->is_relay) {
+            peer_disable_relay_provider(peer);
+        }
+    }
+}
+
+struct peer_data * find_peer_by_id (peerid_t id)
+{
+    for (LinkedList1Node *node = LinkedList1_GetFirst(&peers); node; node = LinkedList1Node_Next(node)) {
+        struct peer_data *peer = UPPER_OBJECT(node, struct peer_data, list_node);
+        if (peer->id == id) {
+            return peer;
+        }
+    }
+    
+    return NULL;
+}
+
+void device_error_handler (void *unused)
+{
+    BLog(BLOG_ERROR, "device error");
+    
+    terminate();
+}
+
+void device_dpsource_handler (void *unused, const uint8_t *frame, int frame_len)
+{
+    ASSERT(frame_len >= 0)
+    ASSERT(frame_len <= device_mtu)
+    
+    // give frame to decider
+    FrameDecider_AnalyzeAndDecide(&frame_decider, frame, frame_len);
+    
+    // forward frame to peers
+    FrameDeciderPeer *decider_peer = FrameDecider_NextDestination(&frame_decider);
+    while (decider_peer) {
+        FrameDeciderPeer *next = FrameDecider_NextDestination(&frame_decider);
+        struct peer_data *peer = UPPER_OBJECT(decider_peer, struct peer_data, decider_peer);
+        DataProtoFlow_Route(&peer->local_dpflow, !!next);
+        decider_peer = next;
+    }
+}
+
+void assign_relays (void)
+{
+    LinkedList1Node *list_node;
+    while (list_node = LinkedList1_GetFirst(&waiting_relay_peers)) {
+        struct peer_data *peer = UPPER_OBJECT(list_node, struct peer_data, waiting_relay_list_node);
+        ASSERT(peer->waiting_relay)
+        
+        ASSERT(!peer->relaying_peer)
+        ASSERT(!peer->have_link)
+        
+        // get a relay
+        LinkedList1Node *list_node2 = LinkedList1_GetFirst(&relays);
+        if (!list_node2) {
+            BLog(BLOG_NOTICE, "no relays");
+            return;
+        }
+        struct peer_data *relay = UPPER_OBJECT(list_node2, struct peer_data, relay_list_node);
+        ASSERT(relay->is_relay)
+        
+        // no longer waiting for relay
+        peer_unregister_need_relay(peer);
+        
+        // install the relay
+        peer_install_relaying(peer, relay);
+    }
+}
+
+char * address_scope_known (uint8_t *name, int name_len)
+{
+    ASSERT(name_len >= 0)
+    
+    for (int i = 0; i < options.num_scopes; i++) {
+        if (name_len == strlen(options.scopes[i]) && !memcmp(name, options.scopes[i], name_len)) {
+            return options.scopes[i];
+        }
+    }
+    
+    return NULL;
+}
+
+void server_handler_error (void *user)
+{
+    BLog(BLOG_ERROR, "server connection failed, exiting");
+    
+    terminate();
+}
+
+void server_handler_ready (void *user, peerid_t param_my_id, uint32_t ext_ip)
+{
+    ASSERT(!server_ready)
+    
+    // remember our ID
+    my_id = param_my_id;
+    
+    // store server reported addresses
+    for (int i = 0; i < num_bind_addrs; i++) {
+        struct bind_addr *addr = &bind_addrs[i];
+        for (int j = 0; j < addr->num_ext_addrs; j++) {
+            struct ext_addr *eaddr = &addr->ext_addrs[j];
+            if (eaddr->server_reported_port >= 0) {
+                if (ext_ip == 0) {
+                    BLog(BLOG_ERROR, "server did not provide our address");
+                    terminate();
+                    return;
+                }
+                BAddr_InitIPv4(&eaddr->addr, ext_ip, hton16(eaddr->server_reported_port));
+                char str[BADDR_MAX_PRINT_LEN];
+                BAddr_Print(&eaddr->addr, str);
+                BLog(BLOG_INFO, "external address (%d,%d): server reported %s", i, j, str);
+            }
+        }
+    }
+    
+    // give receive device the ID
+    DPReceiveDevice_SetPeerID(&device_output_dprd, my_id);
+    
+    // init server queue
+    if (!PacketPassFairQueue_Init(&server_queue, ServerConnection_GetSendInterface(&server), BReactor_PendingGroup(&ss), 0, 1)) {
+        BLog(BLOG_ERROR, "PacketPassFairQueue_Init failed");
+        terminate();
+        return;
+    }
+    
+    // set server ready
+    server_ready = 1;
+    
+    BLog(BLOG_INFO, "server: ready, my ID is %d", (int)my_id);
+}
+
+void server_handler_newclient (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len)
+{
+    ASSERT(server_ready)
+    ASSERT(cert_len >= 0)
+    ASSERT(cert_len <= SCID_NEWCLIENT_MAX_CERT_LEN)
+    
+    // check if the peer already exists
+    if (find_peer_by_id(peer_id)) {
+        BLog(BLOG_WARNING, "server: newclient: peer already known");
+        return;
+    }
+    
+    // make sure it's not the same ID as us
+    if (peer_id == my_id) {
+        BLog(BLOG_WARNING, "server: newclient: peer has our ID");
+        return;
+    }
+    
+    // check if there is spece for the peer
+    if (num_peers >= options.max_peers) {
+        BLog(BLOG_WARNING, "server: newclient: no space for new peer (maximum number reached)");
+        return;
+    }
+    
+    if (!options.ssl && cert_len > 0) {
+        BLog(BLOG_WARNING, "server: newclient: certificate supplied, but not using TLS");
+        return;
+    }
+    
+    peer_add(peer_id, flags, cert, cert_len);
+}
+
+void server_handler_endclient (void *user, peerid_t peer_id)
+{
+    ASSERT(server_ready)
+    
+    // find peer
+    struct peer_data *peer = find_peer_by_id(peer_id);
+    if (!peer) {
+        BLog(BLOG_WARNING, "server: endclient: peer %d not known", (int)peer_id);
+        return;
+    }
+    
+    // remove peer
+    peer_remove(peer, 0);
+}
+
+void server_handler_message (void *user, peerid_t peer_id, uint8_t *data, int data_len)
+{
+    ASSERT(server_ready)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_MSGLEN)
+    
+    // find peer
+    struct peer_data *peer = find_peer_by_id(peer_id);
+    if (!peer) {
+        BLog(BLOG_WARNING, "server: message: peer not known");
+        return;
+    }
+    
+    // make sure we have chat
+    if (!peer->have_chat) {
+        peer_log(peer, BLOG_ERROR, "cannot process message, chat is down");
+        return;
+    }
+    
+    // pass message to chat
+    PeerChat_InputReceived(&peer->chat, data, data_len);
+}
+
+void peer_job_send_seed (struct peer_data *peer)
+{
+    ASSERT(options.transport_mode == TRANSPORT_MODE_UDP)
+    ASSERT(SPPROTO_HAVE_OTP(sp_params))
+    ASSERT(peer->have_link)
+    ASSERT(!peer->pio.udp.sendseed_sent)
+    
+    peer_log(peer, BLOG_DEBUG, "sending OTP send seed");
+    
+    int key_len = BEncryption_cipher_key_size(sp_params.otp_mode);
+    int iv_len = BEncryption_cipher_block_size(sp_params.otp_mode);
+    
+    // generate seed
+    peer->pio.udp.sendseed_sent_id = peer->pio.udp.sendseed_nextid;
+    BRandom_randomize(peer->pio.udp.sendseed_sent_key, key_len);
+    BRandom_randomize(peer->pio.udp.sendseed_sent_iv, iv_len);
+    
+    // set as sent, increment next seed ID
+    peer->pio.udp.sendseed_sent = 1;
+    peer->pio.udp.sendseed_nextid++;
+    
+    // send seed to the peer
+    int msg_len = msg_seed_SIZEseed_id + msg_seed_SIZEkey(key_len) + msg_seed_SIZEiv(iv_len);
+    if (msg_len > MSG_MAX_PAYLOAD) {
+        peer_log(peer, BLOG_ERROR, "OTP send seed message too big");
+        return;
+    }
+    uint8_t *msg;
+    if (!peer_start_msg(peer, (void **)&msg, MSGID_SEED, msg_len)) {
+        return;
+    }
+    msg_seedWriter writer;
+    msg_seedWriter_Init(&writer, msg);
+    msg_seedWriter_Addseed_id(&writer, peer->pio.udp.sendseed_sent_id);
+    uint8_t *key_dst = msg_seedWriter_Addkey(&writer, key_len);
+    memcpy(key_dst, peer->pio.udp.sendseed_sent_key, key_len);
+    uint8_t *iv_dst = msg_seedWriter_Addiv(&writer, iv_len);
+    memcpy(iv_dst, peer->pio.udp.sendseed_sent_iv, iv_len);
+    msg_seedWriter_Finish(&writer);
+    peer_end_msg(peer);
+}
+
+void peer_job_init (struct peer_data *peer)
+{
+    // start setup process
+    if (peer_am_master(peer)) {
+        peer_start_binding(peer);
+    }
+}
+
+struct server_flow * server_flow_init (void)
+{
+    ASSERT(server_ready)
+    
+    // allocate structure
+    struct server_flow *flow = (struct server_flow *)malloc(sizeof(*flow));
+    if (!flow) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // init queue flow
+    PacketPassFairQueueFlow_Init(&flow->qflow, &server_queue);
+    
+    // init connector
+    PacketRecvConnector_Init(&flow->connector, sizeof(struct packetproto_header) + SC_MAX_ENC, BReactor_PendingGroup(&ss));
+    
+    // init encoder buffer
+    if (!SinglePacketBuffer_Init(&flow->encoder_buffer, PacketRecvConnector_GetOutput(&flow->connector), PacketPassFairQueueFlow_GetInput(&flow->qflow), BReactor_PendingGroup(&ss))) {
+        BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail1;
+    }
+    
+    // set not connected
+    flow->connected = 0;
+    
+    return flow;
+    
+fail1:
+    PacketRecvConnector_Free(&flow->connector);
+    PacketPassFairQueueFlow_Free(&flow->qflow);
+    free(flow);
+fail0:
+    return NULL;
+}
+
+void server_flow_free (struct server_flow *flow)
+{
+    PacketPassFairQueueFlow_AssertFree(&flow->qflow);
+    ASSERT(!flow->connected)
+    
+    // remove dying flow reference
+    if (flow == dying_server_flow) {
+        dying_server_flow = NULL;
+    }
+    
+    // free encoder buffer
+    SinglePacketBuffer_Free(&flow->encoder_buffer);
+    
+    // free connector
+    PacketRecvConnector_Free(&flow->connector);
+    
+    // free queue flow
+    PacketPassFairQueueFlow_Free(&flow->qflow);
+    
+    // free structure
+    free(flow);
+}
+
+void server_flow_die (struct server_flow *flow)
+{
+    ASSERT(PacketPassFairQueueFlow_IsBusy(&flow->qflow))
+    ASSERT(!flow->connected)
+    ASSERT(!dying_server_flow)
+    
+    // request notification when flow is done
+    PacketPassFairQueueFlow_SetBusyHandler(&flow->qflow, (PacketPassFairQueue_handler_busy)server_flow_qflow_handler_busy, flow);
+    
+    // set dying flow
+    dying_server_flow = flow;
+}
+
+void server_flow_qflow_handler_busy (struct server_flow *flow)
+{
+    ASSERT(flow == dying_server_flow)
+    ASSERT(!flow->connected)
+    PacketPassFairQueueFlow_AssertFree(&flow->qflow);
+    
+    // finally free flow
+    server_flow_free(flow);
+}
+
+void server_flow_connect (struct server_flow *flow, PacketRecvInterface *input)
+{
+    ASSERT(!flow->connected)
+    ASSERT(flow != dying_server_flow)
+    
+    // connect input
+    PacketRecvConnector_ConnectInput(&flow->connector, input);
+    
+    // set connected
+    flow->connected = 1;
+}
+
+void server_flow_disconnect (struct server_flow *flow)
+{
+    ASSERT(flow->connected)
+    ASSERT(flow != dying_server_flow)
+    
+    // disconnect input
+    PacketRecvConnector_DisconnectInput(&flow->connector);
+    
+    // set not connected
+    flow->connected = 0;
+}
diff --git a/external/badvpn_dns/client/client.h b/external/badvpn_dns/client/client.h
new file mode 100644
index 0000000..595ed59
--- /dev/null
+++ b/external/badvpn_dns/client/client.h
@@ -0,0 +1,193 @@
+/**
+ * @file client.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <protocol/scproto.h>
+#include <structure/LinkedList1.h>
+#include <flow/PacketPassFairQueue.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketRecvConnector.h>
+#include <client/DatagramPeerIO.h>
+#include <client/StreamPeerIO.h>
+#include <client/DataProto.h>
+#include <client/DPReceive.h>
+#include <client/FrameDecider.h>
+#include <client/PeerChat.h>
+#include <client/SinglePacketSource.h>
+
+// NOTE: all time values are in milliseconds
+
+// name of the program
+#define PROGRAM_NAME "client"
+
+// server output buffer size
+#define SERVER_BUFFER_MIN_PACKETS 200
+
+// maximum UDP payload size
+#define CLIENT_UDP_MTU 1472
+
+// maximum number of pending TCP PasswordListener clients
+#define TCP_MAX_PASSWORD_LISTENER_CLIENTS 50
+
+// maximum number of peers
+#define DEFAULT_MAX_PEERS 256
+// maximum number of peer's MAC addresses to remember
+#define PEER_DEFAULT_MAX_MACS 16
+// maximum number of multicast addresses per peer
+#define PEER_DEFAULT_MAX_GROUPS 16
+// how long we wait for a packet to reach full size before sending it (see FragmentProtoDisassembler latency argument)
+#define PEER_DEFAULT_UDP_FRAGMENTATION_LATENCY 0
+// value related to how much out-of-order input we tolerate (see FragmentProtoAssembler num_frames argument)
+#define PEER_UDP_ASSEMBLER_NUM_FRAMES 4
+// socket send buffer (SO_SNDBUF) for peer TCP connections, <=0 to not set
+#define PEER_DEFAULT_TCP_SOCKET_SNDBUF 1048576
+// keep-alive packet interval for p2p communication
+#define PEER_KEEPALIVE_INTERVAL 10000
+// keep-alive receive timer for p2p communication (after how long to consider the link down)
+#define PEER_KEEPALIVE_RECEIVE_TIMER 22000
+// size of frame send buffer, in number of frames
+#define PEER_DEFAULT_SEND_BUFFER_SIZE 32
+// size of frame send buffer for relayed packets, in number of frames
+#define PEER_DEFAULT_SEND_BUFFER_RELAY_SIZE 32
+// time after an unused relay flow is freed (-1 for never)
+#define PEER_RELAY_FLOW_INACTIVITY_TIME 10000
+// retry time
+#define PEER_RETRY_TIME 5000
+
+// for how long a peer can send no Membership Reports for a group
+// before the peer and group are disassociated
+#define DEFAULT_IGMP_GROUP_MEMBERSHIP_INTERVAL 260000
+// how long to wait for joins after a Group Specific query has been
+// forwarded to a peer before assuming there are no listeners at the peer
+#define DEFAULT_IGMP_LAST_MEMBER_QUERY_TIME 2000
+
+// maximum bind addresses
+#define MAX_BIND_ADDRS 8
+// maximum external addresses per bind address
+#define MAX_EXT_ADDRS 8
+// maximum scopes
+#define MAX_SCOPES 8
+
+//#define SIMULATE_PEER_OUT_OF_BUFFER 70
+
+struct server_flow {
+    PacketPassFairQueueFlow qflow;
+    SinglePacketBuffer encoder_buffer;
+    PacketRecvConnector connector;
+    int connected;
+};
+
+struct peer_data {
+    // peer identifier
+    peerid_t id;
+    
+    // flags provided by the server
+    int flags;
+    
+    // certificate reported by the server, defined only if using SSL
+    uint8_t cert[SCID_NEWCLIENT_MAX_CERT_LEN];
+    int cert_len;
+    char *common_name;
+    
+    // init job
+    BPending job_init;
+    
+    // server flow
+    struct server_flow *server_flow;
+    
+    // chat
+    int have_chat;
+    PeerChat chat;
+    int chat_send_msg_len;
+    
+    // resetpeer source (when chat fails)
+    int have_resetpeer;
+    uint8_t resetpeer_packet[sizeof(struct packetproto_header) + sizeof(struct sc_header) + sizeof(struct sc_client_resetpeer)];
+    SinglePacketSource resetpeer_source;
+    
+    // local flow
+    DataProtoFlow local_dpflow;
+    
+    // frame decider peer
+    FrameDeciderPeer decider_peer;
+    
+    // receive peer
+    DPReceivePeer receive_peer;
+    
+    // flag if link objects are initialized
+    int have_link;
+    
+    // receive receiver
+    DPReceiveReceiver receive_receiver;
+    
+    // transport-specific link objects
+    union {
+        struct {
+            DatagramPeerIO pio;
+            uint16_t sendseed_nextid;
+            int sendseed_sent;
+            uint16_t sendseed_sent_id;
+            uint8_t sendseed_sent_key[BENCRYPTION_MAX_KEY_SIZE];
+            uint8_t sendseed_sent_iv[BENCRYPTION_MAX_BLOCK_SIZE];
+            uint16_t pending_recvseed_id;
+            BPending job_send_seed;
+        } udp;
+        struct {
+            StreamPeerIO pio;
+        } tcp;
+    } pio;
+    
+    // link sending
+    DataProtoSink send_dp;
+    
+    // relaying objects
+    struct peer_data *relaying_peer; // peer through which we are relaying, or NULL
+    LinkedList1Node relaying_list_node; // node in relay peer's relay_users
+    
+    // waiting for relay data
+    int waiting_relay;
+    LinkedList1Node waiting_relay_list_node;
+    
+    // retry timer
+    BTimer reset_timer;
+    
+    // relay server specific
+    int is_relay;
+    LinkedList1Node relay_list_node;
+    LinkedList1 relay_users;
+    
+    // binding state
+    int binding;
+    int binding_addrpos;
+    
+    // peers linked list node
+    LinkedList1Node list_node;
+};
diff --git a/external/badvpn_dns/cmake/modules/COPYING-CMAKE-SCRIPTS b/external/badvpn_dns/cmake/modules/COPYING-CMAKE-SCRIPTS
new file mode 100644
index 0000000..4b41776
--- /dev/null
+++ b/external/badvpn_dns/cmake/modules/COPYING-CMAKE-SCRIPTS
@@ -0,0 +1,22 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products 
+   derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/external/badvpn_dns/cmake/modules/FindGLIB2.cmake b/external/badvpn_dns/cmake/modules/FindGLIB2.cmake
new file mode 100644
index 0000000..09fd98d
--- /dev/null
+++ b/external/badvpn_dns/cmake/modules/FindGLIB2.cmake
@@ -0,0 +1,52 @@
+# - Try to find the GLIB2 libraries
+# Once done this will define
+#
+#  GLIB2_FOUND - system has glib2
+#  GLIB2_INCLUDE_DIR - the glib2 include directory
+#  GLIB2_LIBRARIES - glib2 library
+
+# Copyright (c) 2008 Laurent Montel, <montel at kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+
+if(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES)
+    # Already in cache, be silent
+    set(GLIB2_FIND_QUIETLY TRUE)
+endif(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES)
+
+find_package(PkgConfig)
+pkg_check_modules(PC_LibGLIB2 QUIET glib-2.0)
+
+find_path(GLIB2_MAIN_INCLUDE_DIR
+          NAMES glib.h
+          HINTS ${PC_LibGLIB2_INCLUDEDIR}
+          PATH_SUFFIXES glib-2.0)
+
+find_library(GLIB2_LIBRARY 
+             NAMES glib-2.0 
+             HINTS ${PC_LibGLIB2_LIBDIR}
+)
+
+set(GLIB2_LIBRARIES ${GLIB2_LIBRARY})
+
+# search the glibconfig.h include dir under the same root where the library is found
+get_filename_component(glib2LibDir "${GLIB2_LIBRARIES}" PATH)
+
+find_path(GLIB2_INTERNAL_INCLUDE_DIR glibconfig.h
+          PATH_SUFFIXES glib-2.0/include
+          HINTS ${PC_LibGLIB2_INCLUDEDIR} "${glib2LibDir}" ${CMAKE_SYSTEM_LIBRARY_PATH})
+
+set(GLIB2_INCLUDE_DIR "${GLIB2_MAIN_INCLUDE_DIR}")
+
+# not sure if this include dir is optional or required
+# for now it is optional
+if(GLIB2_INTERNAL_INCLUDE_DIR)
+  set(GLIB2_INCLUDE_DIR ${GLIB2_INCLUDE_DIR} "${GLIB2_INTERNAL_INCLUDE_DIR}")
+endif(GLIB2_INTERNAL_INCLUDE_DIR)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GLIB2  DEFAULT_MSG  GLIB2_LIBRARIES GLIB2_MAIN_INCLUDE_DIR)
+
+mark_as_advanced(GLIB2_INCLUDE_DIR GLIB2_LIBRARIES)
diff --git a/external/badvpn_dns/cmake/modules/FindLibraryWithDebug.cmake b/external/badvpn_dns/cmake/modules/FindLibraryWithDebug.cmake
new file mode 100644
index 0000000..58cd730
--- /dev/null
+++ b/external/badvpn_dns/cmake/modules/FindLibraryWithDebug.cmake
@@ -0,0 +1,113 @@
+#
+#  FIND_LIBRARY_WITH_DEBUG
+#  -> enhanced FIND_LIBRARY to allow the search for an
+#     optional debug library with a WIN32_DEBUG_POSTFIX similar
+#     to CMAKE_DEBUG_POSTFIX when creating a shared lib
+#     it has to be the second and third argument
+
+# Copyright (c) 2007, Christian Ehrlicher, <ch.ehrlicher at gmx.de>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+MACRO(FIND_LIBRARY_WITH_DEBUG var_name win32_dbg_postfix_name dgb_postfix libname)
+
+  IF(NOT "${win32_dbg_postfix_name}" STREQUAL "WIN32_DEBUG_POSTFIX")
+
+     # no WIN32_DEBUG_POSTFIX -> simply pass all arguments to FIND_LIBRARY
+     FIND_LIBRARY(${var_name}
+                  ${win32_dbg_postfix_name}
+                  ${dgb_postfix}
+                  ${libname}
+                  ${ARGN}
+     )
+
+  ELSE(NOT "${win32_dbg_postfix_name}" STREQUAL "WIN32_DEBUG_POSTFIX")
+
+    IF(NOT WIN32)
+      # on non-win32 we don't need to take care about WIN32_DEBUG_POSTFIX
+
+      FIND_LIBRARY(${var_name} ${libname} ${ARGN})
+
+    ELSE(NOT WIN32)
+
+      # 1. get all possible libnames
+      SET(args ${ARGN})
+      SET(newargs "")
+      SET(libnames_release "")
+      SET(libnames_debug "")
+
+      LIST(LENGTH args listCount)
+
+      IF("${libname}" STREQUAL "NAMES")
+        SET(append_rest 0)
+        LIST(APPEND args " ")
+
+        FOREACH(i RANGE ${listCount})
+          LIST(GET args ${i} val)
+
+          IF(append_rest)
+            LIST(APPEND newargs ${val})
+          ELSE(append_rest)
+            IF("${val}" STREQUAL "PATHS")
+              LIST(APPEND newargs ${val})
+              SET(append_rest 1)
+            ELSE("${val}" STREQUAL "PATHS")
+              LIST(APPEND libnames_release "${val}")
+              LIST(APPEND libnames_debug   "${val}${dgb_postfix}")
+            ENDIF("${val}" STREQUAL "PATHS")
+          ENDIF(append_rest)
+
+        ENDFOREACH(i)
+
+      ELSE("${libname}" STREQUAL "NAMES")
+
+        # just one name
+        LIST(APPEND libnames_release "${libname}")
+        LIST(APPEND libnames_debug   "${libname}${dgb_postfix}")
+
+        SET(newargs ${args})
+
+      ENDIF("${libname}" STREQUAL "NAMES")
+
+      # search the release lib
+      FIND_LIBRARY(${var_name}_RELEASE
+                   NAMES ${libnames_release}
+                   ${newargs}
+      )
+
+      # search the debug lib
+      FIND_LIBRARY(${var_name}_DEBUG
+                   NAMES ${libnames_debug}
+                   ${newargs}
+      )
+
+      IF(${var_name}_RELEASE AND ${var_name}_DEBUG)
+
+        # both libs found
+        SET(${var_name} optimized ${${var_name}_RELEASE}
+                        debug     ${${var_name}_DEBUG})
+
+      ELSE(${var_name}_RELEASE AND ${var_name}_DEBUG)
+
+        IF(${var_name}_RELEASE)
+
+          # only release found
+          SET(${var_name} ${${var_name}_RELEASE})
+
+        ELSE(${var_name}_RELEASE)
+
+          # only debug (or nothing) found
+          SET(${var_name} ${${var_name}_DEBUG})
+
+        ENDIF(${var_name}_RELEASE)
+       
+      ENDIF(${var_name}_RELEASE AND ${var_name}_DEBUG)
+
+      MARK_AS_ADVANCED(${var_name}_RELEASE)
+      MARK_AS_ADVANCED(${var_name}_DEBUG)
+
+    ENDIF(NOT WIN32)
+
+  ENDIF(NOT "${win32_dbg_postfix_name}" STREQUAL "WIN32_DEBUG_POSTFIX")
+
+ENDMACRO(FIND_LIBRARY_WITH_DEBUG)
diff --git a/external/badvpn_dns/cmake/modules/FindNSPR.cmake b/external/badvpn_dns/cmake/modules/FindNSPR.cmake
new file mode 100644
index 0000000..6e8fed9
--- /dev/null
+++ b/external/badvpn_dns/cmake/modules/FindNSPR.cmake
@@ -0,0 +1,57 @@
+# - Try to find the NSPR library
+# Once done this will define
+#
+#  NSPR_FOUND - system has the NSPR library
+#  NSPR_INCLUDE_DIRS - Include paths needed
+#  NSPR_LIBRARY_DIRS - Linker paths needed
+#  NSPR_LIBRARIES - Libraries needed
+
+# Copyright (c) 2010, Ambroz Bizjak, <ambrop7 at gmail.com>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+include(FindLibraryWithDebug)
+
+if (NSPR_LIBRARIES)
+   set(NSPR_FIND_QUIETLY TRUE)
+endif ()
+
+set(NSPR_FOUND FALSE)
+
+if (WIN32)
+    find_path(NSPR_FIND_INCLUDE_DIR prerror.h)
+
+    FIND_LIBRARY_WITH_DEBUG(NSPR_FIND_LIBRARIES_PLDS WIN32_DEBUG_POSTFIX d NAMES plds4 libplds4)
+    FIND_LIBRARY_WITH_DEBUG(NSPR_FIND_LIBRARIES_PLC WIN32_DEBUG_POSTFIX d NAMES plc4 libplc4)
+    FIND_LIBRARY_WITH_DEBUG(NSPR_FIND_LIBRARIES_NSPR WIN32_DEBUG_POSTFIX d NAMES nspr4 libnspr4)
+
+    if (NSPR_FIND_INCLUDE_DIR AND NSPR_FIND_LIBRARIES_PLDS AND NSPR_FIND_LIBRARIES_PLC AND NSPR_FIND_LIBRARIES_NSPR)
+        set(NSPR_FOUND TRUE)
+        set(NSPR_INCLUDE_DIRS "${NSPR_FIND_INCLUDE_DIR}" CACHE STRING "NSPR include dirs")
+        set(NSPR_LIBRARY_DIRS "" CACHE STRING "NSPR library dirs")
+        set(NSPR_LIBRARIES "${NSPR_FIND_LIBRARIES_PLDS};${NSPR_FIND_LIBRARIES_PLC};${NSPR_FIND_LIBRARIES_NSPR}" CACHE STRING "NSPR libraries")
+    endif ()
+else ()
+    find_package(PkgConfig REQUIRED)
+    pkg_check_modules(NSPR_PC nspr)
+
+    if (NSPR_PC_FOUND)
+        set(NSPR_FOUND TRUE)
+        set(NSPR_INCLUDE_DIRS "${NSPR_PC_INCLUDE_DIRS}" CACHE STRING "NSPR include dirs")
+        set(NSPR_LIBRARY_DIRS "${NSPR_PC_LIBRARY_DIRS}" CACHE STRING "NSPR library dirs")
+        set(NSPR_LIBRARIES "${NSPR_PC_LIBRARIES}" CACHE STRING "NSPR libraries")
+    endif ()
+endif ()
+
+if (NSPR_FOUND)
+    if (NOT NSPR_FIND_QUIETLY)
+        MESSAGE(STATUS "Found NSPR: ${NSPR_INCLUDE_DIRS} ${NSPR_LIBRARY_DIRS} ${NSPR_LIBRARIES}")
+    endif ()
+else ()
+    if (NSPR_FIND_REQUIRED)
+        message(FATAL_ERROR "Could NOT find NSPR")
+    endif ()
+endif ()
+
+mark_as_advanced(NSPR_INCLUDE_DIRS NSPR_LIBRARY_DIRS NSPR_LIBRARIES)
diff --git a/external/badvpn_dns/cmake/modules/FindNSS.cmake b/external/badvpn_dns/cmake/modules/FindNSS.cmake
new file mode 100644
index 0000000..17fd45a
--- /dev/null
+++ b/external/badvpn_dns/cmake/modules/FindNSS.cmake
@@ -0,0 +1,57 @@
+# - Try to find the NSS library
+# Once done this will define
+#
+#  NSS_FOUND - system has the NSS library
+#  NSS_INCLUDE_DIRS - Include paths needed
+#  NSS_LIBRARY_DIRS - Linker paths needed
+#  NSS_LIBRARIES - Libraries needed
+
+# Copyright (c) 2010, Ambroz Bizjak, <ambrop7 at gmail.com>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+include(FindLibraryWithDebug)
+
+if (NSS_LIBRARIES)
+   set(NSS_FIND_QUIETLY TRUE)
+endif ()
+
+set(NSS_FOUND FALSE)
+
+if (WIN32)
+    find_path(NSS_FIND_INCLUDE_DIR nss.h)
+
+    FIND_LIBRARY_WITH_DEBUG(NSS_FIND_LIBRARIES_SSL WIN32_DEBUG_POSTFIX d NAMES ssl3)
+    FIND_LIBRARY_WITH_DEBUG(NSS_FIND_LIBRARIES_SMIME WIN32_DEBUG_POSTFIX d NAMES smime3)
+    FIND_LIBRARY_WITH_DEBUG(NSS_FIND_LIBRARIES_NSS WIN32_DEBUG_POSTFIX d NAMES nss3)
+
+    if (NSS_FIND_INCLUDE_DIR AND NSS_FIND_LIBRARIES_SSL AND NSS_FIND_LIBRARIES_SMIME AND NSS_FIND_LIBRARIES_NSS)
+        set(NSS_FOUND TRUE)
+        set(NSS_INCLUDE_DIRS "${NSS_FIND_INCLUDE_DIR}" CACHE STRING "NSS include dirs")
+        set(NSS_LIBRARY_DIRS "" CACHE STRING "NSS library dirs")
+        set(NSS_LIBRARIES "${NSS_FIND_LIBRARIES_SSL};${NSS_FIND_LIBRARIES_SMIME};${NSS_FIND_LIBRARIES_NSS}" CACHE STRING "NSS libraries")
+    endif ()
+else ()
+    find_package(PkgConfig REQUIRED)
+    pkg_check_modules(NSS_PC nss)
+
+    if (NSS_PC_FOUND)
+        set(NSS_FOUND TRUE)
+        set(NSS_INCLUDE_DIRS "${NSS_PC_INCLUDE_DIRS}" CACHE STRING "NSS include dirs")
+        set(NSS_LIBRARY_DIRS "${NSS_PC_LIBRARY_DIRS}" CACHE STRING "NSS library dirs")
+        set(NSS_LIBRARIES "${NSS_PC_LIBRARIES}" CACHE STRING "NSS libraries")
+    endif ()
+endif ()
+
+if (NSS_FOUND)
+    if (NOT NSS_FIND_QUIETLY)
+        MESSAGE(STATUS "Found NSS: ${NSS_INCLUDE_DIRS} ${NSS_LIBRARY_DIRS} ${NSS_LIBRARIES}")
+    endif ()
+else ()
+    if (NSS_FIND_REQUIRED)
+        message(FATAL_ERROR "Could NOT find NSS")
+    endif ()
+endif ()
+
+mark_as_advanced(NSS_INCLUDE_DIRS NSS_LIBRARY_DIRS NSS_LIBRARIES)
diff --git a/external/badvpn_dns/cmake/modules/FindOpenSSL.cmake b/external/badvpn_dns/cmake/modules/FindOpenSSL.cmake
new file mode 100644
index 0000000..4434e95
--- /dev/null
+++ b/external/badvpn_dns/cmake/modules/FindOpenSSL.cmake
@@ -0,0 +1,72 @@
+# - Try to find the OpenSSL library
+# Once done this will define
+#
+#  OpenSSL_FOUND - system has the OpenSSL library
+#  OpenSSL_INCLUDE_DIRS - Include paths needed
+#  OpenSSL_LIBRARY_DIRS - Linker paths needed
+#  OpenSSL_LIBRARIES - Libraries needed
+
+# Copyright (c) 2010, Ambroz Bizjak, <ambrop7 at gmail.com>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+include(FindLibraryWithDebug)
+
+if (OpenSSL_LIBRARIES)
+   set(OpenSSL_FIND_QUIETLY TRUE)
+endif ()
+
+set(OpenSSL_FOUND FALSE)
+
+if (WIN32)
+    find_path(OpenSSL_FIND_INCLUDE_DIR openssl/ssl.h)
+
+    if (OpenSSL_FIND_INCLUDE_DIR)
+        # look for libraries built with GCC
+        find_library(OpenSSL_FIND_LIBRARIES_SSL NAMES ssl)
+        find_library(OpenSSL_FIND_LIBRARIES_CRYPTO NAMES crypto)
+
+        if (OpenSSL_FIND_LIBRARIES_SSL AND OpenSSL_FIND_LIBRARIES_CRYPTO)
+            set(OpenSSL_FOUND TRUE)
+            set(OpenSSL_LIBRARY_DIRS "" CACHE STRING "OpenSSL library dirs")
+            set(OpenSSL_LIBRARIES "${OpenSSL_FIND_LIBRARIES_SSL};${OpenSSL_FIND_LIBRARIES_CRYPTO}" CACHE STRING "OpenSSL libraries")
+        else ()
+            # look for libraries built with MSVC
+            FIND_LIBRARY_WITH_DEBUG(OpenSSL_FIND_LIBRARIES_SSL WIN32_DEBUG_POSTFIX d NAMES ssl ssleay ssleay32 libssleay32 ssleay32MD)
+            FIND_LIBRARY_WITH_DEBUG(OpenSSL_FIND_LIBRARIES_EAY WIN32_DEBUG_POSTFIX d NAMES eay libeay libeay32 libeay32MD)
+
+            if (OpenSSL_FIND_LIBRARIES_SSL AND OpenSSL_FIND_LIBRARIES_EAY)
+                set(OpenSSL_FOUND TRUE)
+                set(OpenSSL_LIBRARY_DIRS "" CACHE STRING "OpenSSL library dirs")
+                set(OpenSSL_LIBRARIES "${OpenSSL_FIND_LIBRARIES_SSL};${OpenSSL_FIND_LIBRARIES_EAY}" CACHE STRING "OpenSSL libraries")
+            endif ()
+        endif ()
+
+        if (OpenSSL_FOUND)
+            set(OpenSSL_INCLUDE_DIRS "${OpenSSL_FIND_INCLUDE_DIR}" CACHE STRING "OpenSSL include dirs")
+        endif ()
+    endif ()
+else ()
+    find_package(PkgConfig REQUIRED)
+    pkg_check_modules(OpenSSL_PC openssl)
+
+    if (OpenSSL_PC_FOUND)
+        set(OpenSSL_FOUND TRUE)
+        set(OpenSSL_INCLUDE_DIRS "${OpenSSL_PC_INCLUDE_DIRS}" CACHE STRING "OpenSSL include dirs")
+        set(OpenSSL_LIBRARY_DIRS "${OpenSSL_PC_LIBRARY_DIRS}" CACHE STRING "OpenSSL library dirs")
+        set(OpenSSL_LIBRARIES "${OpenSSL_PC_LIBRARIES}" CACHE STRING "OpenSSL libraries")
+    endif ()
+endif ()
+
+if (OpenSSL_FOUND)
+    if (NOT OpenSSL_FIND_QUIETLY)
+        MESSAGE(STATUS "Found OpenSSL: ${OpenSSL_INCLUDE_DIRS} ${OpenSSL_LIBRARY_DIRS} ${OpenSSL_LIBRARIES}")
+    endif ()
+else ()
+    if (OpenSSL_FIND_REQUIRED)
+        message(FATAL_ERROR "Could NOT find OpenSSL")
+    endif ()
+endif ()
+
+mark_as_advanced(OpenSSL_INCLUDE_DIRS OpenSSL_LIBRARY_DIRS OpenSSL_LIBRARIES)
diff --git a/external/badvpn_dns/compile-tun2sock.sh b/external/badvpn_dns/compile-tun2sock.sh
new file mode 100755
index 0000000..fbc2388
--- /dev/null
+++ b/external/badvpn_dns/compile-tun2sock.sh
@@ -0,0 +1,112 @@
+#!/bin/bash
+#
+# Compiles tun2socks for Linux.
+# Intended as a convenience if you don't want to deal with CMake.
+
+# Input environment vars:
+#   SRCDIR - BadVPN source code
+#   CC - compiler
+#   CFLAGS - compiler compile flags
+#   LDFLAGS - compiler link flags
+#   ENDIAN - "little" or "big"
+#   KERNEL - "2.6" or "2.4", default "2.6"
+#
+# Puts object files and the executable in the working directory.
+#
+
+if [[ -z $SRCDIR ]] || [[ ! -e $SRCDIR/CMakeLists.txt ]]; then
+    echo "SRCDIR is wrong"
+    exit 1
+fi
+
+if ! "${CC}" --version &>/dev/null; then
+    echo "CC is wrong"
+    exit 1
+fi
+
+if [[ $ENDIAN != "little" ]] && [[ $ENDIAN != "big" ]]; then
+    echo "ENDIAN is wrong"
+    exit 1
+fi
+
+if [[ -z $KERNEL ]]; then
+    KERNEL="2.6"
+elif [[ $KERNEL != "2.6" ]] && [[ $KERNEL != "2.4" ]]; then
+    echo "KERNEL is wrong"
+    exit 1
+fi
+
+CFLAGS="${CFLAGS} -std=gnu99"
+INCLUDES=( "-I${SRCDIR}" "-I${SRCDIR}/lwip/src/include/ipv4" "-I${SRCDIR}/lwip/src/include/ipv6" "-I${SRCDIR}/lwip/src/include" "-I${SRCDIR}/lwip/custom" )
+DEFS=( -DBADVPN_THREAD_SAFE=0 -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE )
+
+[[ $KERNEL = "2.4" ]] && DEFS=( "${DEFS[@]}" -DBADVPN_USE_SELFPIPE -DBADVPN_USE_POLL ) || DEFS=( "${DEFS[@]}" -DBADVPN_USE_SIGNALFD -DBADVPN_USE_EPOLL )
+
+[[ $ENDIAN = "little" ]] && DEFS=( "${DEFS[@]}" -DBADVPN_LITTLE_ENDIAN ) || DEFS=( "${DEFS[@]}" -DBADVPN_BIG_ENDIAN )
+    
+SOURCES="
+base/BLog_syslog.c
+system/BReactor_badvpn.c
+system/BSignal.c
+system/BConnection_unix.c
+system/BTime.c
+system/BUnixSignal.c
+system/BNetwork.c
+flow/StreamRecvInterface.c
+flow/PacketRecvInterface.c
+flow/PacketPassInterface.c
+flow/StreamPassInterface.c
+flow/SinglePacketBuffer.c
+flow/BufferWriter.c
+flow/PacketBuffer.c
+flow/PacketStreamSender.c
+flow/PacketPassConnector.c
+flow/PacketProtoFlow.c
+flow/PacketPassFairQueue.c
+flow/PacketProtoEncoder.c
+flow/PacketProtoDecoder.c
+socksclient/BSocksClient.c
+tuntap/BTap.c
+lwip/src/core/timers.c
+lwip/src/core/udp.c
+lwip/src/core/memp.c
+lwip/src/core/init.c
+lwip/src/core/pbuf.c
+lwip/src/core/tcp.c
+lwip/src/core/tcp_out.c
+lwip/src/core/netif.c
+lwip/src/core/def.c
+lwip/src/core/mem.c
+lwip/src/core/tcp_in.c
+lwip/src/core/stats.c
+lwip/src/core/inet_chksum.c
+lwip/src/core/ipv4/icmp.c
+lwip/src/core/ipv4/ip4.c
+lwip/src/core/ipv4/ip4_addr.c
+lwip/src/core/ipv4/ip_frag.c
+lwip/src/core/ipv6/ip6.c
+lwip/src/core/ipv6/nd6.c
+lwip/src/core/ipv6/icmp6.c
+lwip/src/core/ipv6/ip6_addr.c
+lwip/src/core/ipv6/ip6_frag.c
+lwip/custom/sys.c
+tun2socks/tun2socks.c
+base/DebugObject.c
+base/BLog.c
+base/BPending.c
+flowextra/PacketPassInactivityMonitor.c
+tun2socks/SocksUdpGwClient.c
+udpgw_client/UdpGwClient.c
+"
+
+set -e
+set -x
+
+OBJS=()
+for f in $SOURCES; do
+    obj=$(basename "${f}").o
+    "${CC}" -c ${CFLAGS} "${INCLUDES[@]}" "${DEFS[@]}" "${SRCDIR}/${f}" -o "${obj}"
+    OBJS=( "${OBJS[@]}" "${obj}" )
+done
+
+"${CC}" ${LDFLAGS} "${OBJS[@]}" -o tun2socks -lrt
diff --git a/external/badvpn_dns/compile-udpgw.sh b/external/badvpn_dns/compile-udpgw.sh
new file mode 100755
index 0000000..5f132f9
--- /dev/null
+++ b/external/badvpn_dns/compile-udpgw.sh
@@ -0,0 +1,84 @@
+#!/bin/bash
+#
+# Compiles udpgw for Linux.
+# Intended as a convenience if you don't want to deal with CMake.
+
+# Input environment vars:
+#   SRCDIR - BadVPN source code
+#   CC - compiler
+#   CFLAGS - compiler compile flags
+#   LDFLAGS - compiler link flags
+#   ENDIAN - "little" or "big"
+#   KERNEL - "2.6" or "2.4", default "2.6"
+#
+# Puts object files and the executable in the working directory.
+#
+
+if [[ -z $SRCDIR ]] || [[ ! -e $SRCDIR/CMakeLists.txt ]]; then
+    echo "SRCDIR is wrong"
+    exit 1
+fi
+
+if ! "${CC}" --version &>/dev/null; then
+    echo "CC is wrong"
+    exit 1
+fi
+
+if [[ $ENDIAN != "little" ]] && [[ $ENDIAN != "big" ]]; then
+    echo "ENDIAN is wrong"
+    exit 1
+fi
+
+if [[ -z $KERNEL ]]; then
+    KERNEL="2.6"
+elif [[ $KERNEL != "2.6" ]] && [[ $KERNEL != "2.4" ]]; then
+    echo "KERNEL is wrong"
+    exit 1
+fi
+
+CFLAGS="${CFLAGS} -std=gnu99"
+INCLUDES=( "-I${SRCDIR}" )
+DEFS=( -DBADVPN_THREAD_SAFE=0 -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE )
+
+[[ $KERNEL = "2.4" ]] && DEFS=( "${DEFS[@]}" -DBADVPN_USE_SELFPIPE -DBADVPN_USE_POLL ) || DEFS=( "${DEFS[@]}" -DBADVPN_USE_SIGNALFD -DBADVPN_USE_EPOLL )
+
+[[ $ENDIAN = "little" ]] && DEFS=( "${DEFS[@]}" -DBADVPN_LITTLE_ENDIAN ) || DEFS=( "${DEFS[@]}" -DBADVPN_BIG_ENDIAN )
+    
+SOURCES="
+base/BLog_syslog.c
+system/BReactor_badvpn.c
+system/BSignal.c
+system/BConnection_unix.c
+system/BDatagram_unix.c
+system/BTime.c
+system/BUnixSignal.c
+system/BNetwork.c
+flow/StreamRecvInterface.c
+flow/PacketRecvInterface.c
+flow/PacketPassInterface.c
+flow/StreamPassInterface.c
+flow/SinglePacketBuffer.c
+flow/BufferWriter.c
+flow/PacketBuffer.c
+flow/PacketStreamSender.c
+flow/PacketProtoFlow.c
+flow/PacketPassFairQueue.c
+flow/PacketProtoEncoder.c
+flow/PacketProtoDecoder.c
+base/DebugObject.c
+base/BLog.c
+base/BPending.c
+udpgw/udpgw.c
+"
+
+set -e
+set -x
+
+OBJS=()
+for f in $SOURCES; do
+    obj=$(basename "${f}").o
+    "${CC}" -c ${CFLAGS} "${INCLUDES[@]}" "${DEFS[@]}" "${SRCDIR}/${f}" -o "${obj}"
+    OBJS=( "${OBJS[@]}" "${obj}" )
+done
+
+"${CC}" ${LDFLAGS} "${OBJS[@]}" -o udpgw -lrt
diff --git a/external/badvpn_dns/dhcpclient/BDHCPClient.c b/external/badvpn_dns/dhcpclient/BDHCPClient.c
new file mode 100644
index 0000000..70ee6ad
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/BDHCPClient.c
@@ -0,0 +1,340 @@
+/**
+ * @file BDHCPClient.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <sys/ioctl.h>
+#include <linux/filter.h>
+
+#include <misc/debug.h>
+#include <misc/byteorder.h>
+#include <misc/ethernet_proto.h>
+#include <misc/ipv4_proto.h>
+#include <misc/udp_proto.h>
+#include <misc/dhcp_proto.h>
+#include <misc/get_iface_info.h>
+#include <base/BLog.h>
+
+#include <dhcpclient/BDHCPClient.h>
+
+#include <generated/blog_channel_BDHCPClient.h>
+
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+#define IPUDP_OVERHEAD (sizeof(struct ipv4_header) + sizeof(struct udp_header))
+
+static const struct sock_filter dhcp_sock_filter[] = {
+    BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 9),                        // A <- IP protocol
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPV4_PROTOCOL_UDP, 0, 3), // IP protocol = UDP ?
+    BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 22),                       // A <- UDP destination port
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_CLIENT_PORT, 0, 1),  // UDP destination port = DHCP client ?
+    BPF_STMT(BPF_RET + BPF_K, 65535),                             // return all
+    BPF_STMT(BPF_RET + BPF_K, 0)                                  // ignore
+};
+
+static void dgram_handler (BDHCPClient *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    BLog(BLOG_ERROR, "packet socket error");
+    
+    // report error
+    DEBUGERROR(&o->d_err, o->handler(o->user, BDHCPCLIENT_EVENT_ERROR));
+    return;
+}
+
+static void dhcp_handler (BDHCPClient *o, int event)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    switch (event) {
+        case BDHCPCLIENTCORE_EVENT_UP:
+            ASSERT(!o->up)
+            o->up = 1;
+            o->handler(o->user, BDHCPCLIENT_EVENT_UP);
+            return;
+            
+        case BDHCPCLIENTCORE_EVENT_DOWN:
+            ASSERT(o->up)
+            o->up = 0;
+            o->handler(o->user, BDHCPCLIENT_EVENT_DOWN);
+            return;
+            
+        default:
+            ASSERT(0);
+    }
+}
+
+static void dhcp_func_getsendermac (BDHCPClient *o, uint8_t *out_mac)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    BAddr remote_addr;
+    BIPAddr local_addr;
+    if (!BDatagram_GetLastReceiveAddrs(&o->dgram, &remote_addr, &local_addr)) {
+        BLog(BLOG_ERROR, "BDatagram_GetLastReceiveAddrs failed");
+        goto fail;
+    }
+    
+    if (remote_addr.type != BADDR_TYPE_PACKET) {
+        BLog(BLOG_ERROR, "address type invalid");
+        goto fail;
+    }
+    
+    if (remote_addr.packet.header_type != BADDR_PACKET_HEADER_TYPE_ETHERNET) {
+        BLog(BLOG_ERROR, "address header type invalid");
+        goto fail;
+    }
+    
+    memcpy(out_mac, remote_addr.packet.phys_addr, 6);
+    return;
+    
+fail:
+    memset(out_mac, 0, 6);
+}
+
+int BDHCPClient_Init (BDHCPClient *o, const char *ifname, struct BDHCPClient_opts opts, BReactor *reactor, BRandom2 *random2, BDHCPClient_handler handler, void *user)
+{
+    // init arguments
+    o->reactor = reactor;
+    o->handler = handler;
+    o->user = user;
+    
+    // get interface information
+    uint8_t if_mac[6];
+    int if_mtu;
+    int if_index;
+    if (!badvpn_get_iface_info(ifname, if_mac, &if_mtu, &if_index)) {
+        BLog(BLOG_ERROR, "failed to get interface information");
+        goto fail0;
+    }
+    
+    BLog(BLOG_INFO, "if_mac=%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8" if_mtu=%d if_index=%d",
+          if_mac[0], if_mac[1], if_mac[2], if_mac[3], if_mac[4], if_mac[5], if_mtu, if_index);
+    
+    if (if_mtu < IPUDP_OVERHEAD) {
+        BLog(BLOG_ERROR, "MTU is too small for UDP/IP !?!");
+        goto fail0;
+    }
+    
+    int dhcp_mtu = if_mtu - IPUDP_OVERHEAD;
+    
+    // init dgram
+    if (!BDatagram_Init(&o->dgram, BADDR_TYPE_PACKET, o->reactor, o, (BDatagram_handler)dgram_handler)) {
+        BLog(BLOG_ERROR, "BDatagram_Init failed");
+        goto fail0;
+    }
+    
+    // set socket filter
+    {
+        struct sock_filter filter[sizeof(dhcp_sock_filter) / sizeof(dhcp_sock_filter[0])];
+        memcpy(filter, dhcp_sock_filter, sizeof(filter));
+        struct sock_fprog fprog = {
+            .len = sizeof(filter) / sizeof(filter[0]),
+            .filter = filter
+        };
+        if (setsockopt(BDatagram_GetFd(&o->dgram), SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) {
+            BLog(BLOG_NOTICE, "not using socket filter");
+        }
+    }
+    
+    // bind dgram
+    BAddr bind_addr;
+    BAddr_InitPacket(&bind_addr, hton16(ETHERTYPE_IPV4), if_index, BADDR_PACKET_HEADER_TYPE_ETHERNET, BADDR_PACKET_PACKET_TYPE_HOST, if_mac);
+    if (!BDatagram_Bind(&o->dgram, bind_addr)) {
+        BLog(BLOG_ERROR, "BDatagram_Bind failed");
+        goto fail1;
+    }
+    
+    // set dgram send addresses
+    BAddr dest_addr;
+    uint8_t broadcast_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+    BAddr_InitPacket(&dest_addr, hton16(ETHERTYPE_IPV4), if_index, BADDR_PACKET_HEADER_TYPE_ETHERNET, BADDR_PACKET_PACKET_TYPE_BROADCAST, broadcast_mac);
+    BIPAddr local_addr;
+    BIPAddr_InitInvalid(&local_addr);
+    BDatagram_SetSendAddrs(&o->dgram, dest_addr, local_addr);
+
+    // init dgram interfaces
+    BDatagram_SendAsync_Init(&o->dgram, if_mtu);
+    BDatagram_RecvAsync_Init(&o->dgram, if_mtu);
+    
+    // init sending
+    
+    // init copier
+    PacketCopier_Init(&o->send_copier, dhcp_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // init encoder
+    DHCPIpUdpEncoder_Init(&o->send_encoder, PacketCopier_GetOutput(&o->send_copier), BReactor_PendingGroup(o->reactor));
+    
+    // init buffer
+    if (!SinglePacketBuffer_Init(&o->send_buffer, DHCPIpUdpEncoder_GetOutput(&o->send_encoder), BDatagram_SendAsync_GetIf(&o->dgram), BReactor_PendingGroup(o->reactor))) {
+        BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail2;
+    }
+    
+    // init receiving
+    
+    // init copier
+    PacketCopier_Init(&o->recv_copier, dhcp_mtu, BReactor_PendingGroup(o->reactor));
+    
+    // init decoder
+    DHCPIpUdpDecoder_Init(&o->recv_decoder, PacketCopier_GetInput(&o->recv_copier), BReactor_PendingGroup(o->reactor));
+    
+    // init buffer
+    if (!SinglePacketBuffer_Init(&o->recv_buffer, BDatagram_RecvAsync_GetIf(&o->dgram), DHCPIpUdpDecoder_GetInput(&o->recv_decoder), BReactor_PendingGroup(o->reactor))) {
+        BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed");
+        goto fail3;
+    }
+    
+    // init options
+    struct BDHCPClientCore_opts core_opts;
+    core_opts.hostname = opts.hostname;
+    core_opts.vendorclassid = opts.vendorclassid;
+    core_opts.clientid = opts.clientid;
+    core_opts.clientid_len = opts.clientid_len;
+    
+    // auto-generate clientid from MAC if requested
+    uint8_t mac_cid[7];
+    if (opts.auto_clientid) {
+        mac_cid[0] = DHCP_HARDWARE_ADDRESS_TYPE_ETHERNET;
+        memcpy(mac_cid + 1, if_mac, 6);
+        core_opts.clientid = mac_cid;
+        core_opts.clientid_len = sizeof(mac_cid);
+    }
+    
+    // init dhcp
+    if (!BDHCPClientCore_Init(&o->dhcp, PacketCopier_GetInput(&o->send_copier), PacketCopier_GetOutput(&o->recv_copier), if_mac, core_opts, o->reactor, random2, o,
+                              (BDHCPClientCore_func_getsendermac)dhcp_func_getsendermac,
+                              (BDHCPClientCore_handler)dhcp_handler
+    )) {
+        BLog(BLOG_ERROR, "BDHCPClientCore_Init failed");
+        goto fail4;
+    }
+    
+    // set not up
+    o->up = 0;
+    
+    DebugError_Init(&o->d_err, BReactor_PendingGroup(o->reactor));
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail4:
+    SinglePacketBuffer_Free(&o->recv_buffer);
+fail3:
+    DHCPIpUdpDecoder_Free(&o->recv_decoder);
+    PacketCopier_Free(&o->recv_copier);
+    SinglePacketBuffer_Free(&o->send_buffer);
+fail2:
+    DHCPIpUdpEncoder_Free(&o->send_encoder);
+    PacketCopier_Free(&o->send_copier);
+    BDatagram_RecvAsync_Free(&o->dgram);
+    BDatagram_SendAsync_Free(&o->dgram);
+fail1:
+    BDatagram_Free(&o->dgram);
+fail0:
+    return 0;
+}
+
+void BDHCPClient_Free (BDHCPClient *o)
+{
+    DebugObject_Free(&o->d_obj);
+    DebugError_Free(&o->d_err);
+    
+    // free dhcp
+    BDHCPClientCore_Free(&o->dhcp);
+    
+    // free receiving
+    SinglePacketBuffer_Free(&o->recv_buffer);
+    DHCPIpUdpDecoder_Free(&o->recv_decoder);
+    PacketCopier_Free(&o->recv_copier);
+    
+    // free sending
+    SinglePacketBuffer_Free(&o->send_buffer);
+    DHCPIpUdpEncoder_Free(&o->send_encoder);
+    PacketCopier_Free(&o->send_copier);
+    
+    // free dgram interfaces
+    BDatagram_RecvAsync_Free(&o->dgram);
+    BDatagram_SendAsync_Free(&o->dgram);
+    
+    // free dgram
+    BDatagram_Free(&o->dgram);
+}
+
+int BDHCPClient_IsUp (BDHCPClient *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return o->up;
+}
+
+void BDHCPClient_GetClientIP (BDHCPClient *o, uint32_t *out_ip)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->up)
+    
+    BDHCPClientCore_GetClientIP(&o->dhcp, out_ip);
+}
+
+void BDHCPClient_GetClientMask (BDHCPClient *o, uint32_t *out_mask)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->up)
+    
+    BDHCPClientCore_GetClientMask(&o->dhcp, out_mask);
+}
+
+int BDHCPClient_GetRouter (BDHCPClient *o, uint32_t *out_router)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->up)
+    
+    return BDHCPClientCore_GetRouter(&o->dhcp, out_router);
+}
+
+int BDHCPClient_GetDNS (BDHCPClient *o, uint32_t *out_dns_servers, size_t max_dns_servers)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->up)
+    
+    return BDHCPClientCore_GetDNS(&o->dhcp, out_dns_servers, max_dns_servers);
+}
+
+void BDHCPClient_GetServerMAC (BDHCPClient *o, uint8_t *out_mac)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->up)
+    
+    BDHCPClientCore_GetServerMAC(&o->dhcp, out_mac);
+}
diff --git a/external/badvpn_dns/dhcpclient/BDHCPClient.h b/external/badvpn_dns/dhcpclient/BDHCPClient.h
new file mode 100644
index 0000000..c0da0c4
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/BDHCPClient.h
@@ -0,0 +1,87 @@
+/**
+ * @file BDHCPClient.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * DHCP client.
+ */
+
+#ifndef BADVPN_DHCPCLIENT_BDHCPCLIENT_H
+#define BADVPN_DHCPCLIENT_BDHCPCLIENT_H
+
+#include <base/DebugObject.h>
+#include <system/BDatagram.h>
+#include <flow/PacketCopier.h>
+#include <flow/SinglePacketBuffer.h>
+#include <dhcpclient/BDHCPClientCore.h>
+#include <dhcpclient/DHCPIpUdpDecoder.h>
+#include <dhcpclient/DHCPIpUdpEncoder.h>
+
+#define BDHCPCLIENT_EVENT_UP 1
+#define BDHCPCLIENT_EVENT_DOWN 2
+#define BDHCPCLIENT_EVENT_ERROR 3
+
+#define BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS
+
+typedef void (*BDHCPClient_handler) (void *user, int event);
+
+typedef struct {
+    BReactor *reactor;
+    BDatagram dgram;
+    BDHCPClient_handler handler;
+    void *user;
+    PacketCopier send_copier;
+    DHCPIpUdpEncoder send_encoder;
+    SinglePacketBuffer send_buffer;
+    SinglePacketBuffer recv_buffer;
+    DHCPIpUdpDecoder recv_decoder;
+    PacketCopier recv_copier;
+    BDHCPClientCore dhcp;
+    int up;
+    DebugError d_err;
+    DebugObject d_obj;
+} BDHCPClient;
+
+struct BDHCPClient_opts {
+    const char *hostname;
+    const char *vendorclassid;
+    const uint8_t *clientid;
+    size_t clientid_len;
+    int auto_clientid;
+};
+
+int BDHCPClient_Init (BDHCPClient *o, const char *ifname, struct BDHCPClient_opts opts, BReactor *reactor, BRandom2 *random2, BDHCPClient_handler handler, void *user);
+void BDHCPClient_Free (BDHCPClient *o);
+int BDHCPClient_IsUp (BDHCPClient *o);
+void BDHCPClient_GetClientIP (BDHCPClient *o, uint32_t *out_ip);
+void BDHCPClient_GetClientMask (BDHCPClient *o, uint32_t *out_mask);
+int BDHCPClient_GetRouter (BDHCPClient *o, uint32_t *out_router);
+int BDHCPClient_GetDNS (BDHCPClient *o, uint32_t *out_dns_servers, size_t max_dns_servers);
+void BDHCPClient_GetServerMAC (BDHCPClient *o, uint8_t *out_mac);
+
+#endif
diff --git a/external/badvpn_dns/dhcpclient/BDHCPClientCore.c b/external/badvpn_dns/dhcpclient/BDHCPClientCore.c
new file mode 100644
index 0000000..5a605e4
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/BDHCPClientCore.c
@@ -0,0 +1,860 @@
+/**
+ * @file BDHCPClientCore.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <misc/byteorder.h>
+#include <misc/minmax.h>
+#include <misc/balloc.h>
+#include <misc/bsize.h>
+#include <misc/dhcp_proto.h>
+#include <base/BLog.h>
+
+#include <dhcpclient/BDHCPClientCore.h>
+
+#include <generated/blog_channel_BDHCPClientCore.h>
+
+#define RESET_TIMEOUT 4000
+#define REQUEST_TIMEOUT 3000
+#define RENEW_REQUEST_TIMEOUT 20000
+#define MAX_REQUESTS 4
+#define RENEW_TIMEOUT(lease) ((btime_t)500 * (lease))
+#define XID_REUSE_MAX 8
+
+#define LEASE_TIMEOUT(lease) ((btime_t)1000 * (lease) - RENEW_TIMEOUT(lease))
+
+#define STATE_RESETTING 1
+#define STATE_SENT_DISCOVER 2
+#define STATE_SENT_REQUEST 3
+#define STATE_FINISHED 4
+#define STATE_RENEWING 5
+
+#define IP_UDP_HEADERS_SIZE 28
+
+static void report_up (BDHCPClientCore *o)
+{
+    o->handler(o->user, BDHCPCLIENTCORE_EVENT_UP);
+    return;
+}
+
+static void report_down (BDHCPClientCore *o)
+{
+    o->handler(o->user, BDHCPCLIENTCORE_EVENT_DOWN);
+    return;
+}
+
+static void send_message (
+    BDHCPClientCore *o,
+    int type,
+    uint32_t xid,
+    int have_requested_ip_address, uint32_t requested_ip_address,
+    int have_dhcp_server_identifier, uint32_t dhcp_server_identifier
+)
+{
+    ASSERT(type == DHCP_MESSAGE_TYPE_DISCOVER || type == DHCP_MESSAGE_TYPE_REQUEST)
+    
+    if (o->sending) {
+        BLog(BLOG_ERROR, "already sending");
+        return;
+    }
+    
+    // write header
+    struct dhcp_header header;
+    memset(&header, 0, sizeof(header));
+    header.op = hton8(DHCP_OP_BOOTREQUEST);
+    header.htype = hton8(DHCP_HARDWARE_ADDRESS_TYPE_ETHERNET);
+    header.hlen = hton8(6);
+    header.xid = xid;
+    header.secs = hton16(0);
+    memcpy(header.chaddr, o->client_mac_addr, sizeof(o->client_mac_addr));
+    header.magic = hton32(DHCP_MAGIC);
+    memcpy(o->send_buf, &header, sizeof(header));
+    
+    // write options
+    
+    char *out = o->send_buf + sizeof(header);
+    struct dhcp_option_header oh;
+    
+    // DHCP message type
+    {
+        oh.type = hton8(DHCP_OPTION_DHCP_MESSAGE_TYPE);
+        oh.len = hton8(sizeof(struct dhcp_option_dhcp_message_type));
+        struct dhcp_option_dhcp_message_type opt;
+        opt.type = hton8(type);
+        memcpy(out, &oh, sizeof(oh));
+        memcpy(out + sizeof(oh), &opt, sizeof(opt));
+        out += sizeof(oh) + sizeof(opt);
+    }
+    
+    if (have_requested_ip_address) {
+        // requested IP address
+        oh.type = hton8(DHCP_OPTION_REQUESTED_IP_ADDRESS);
+        oh.len = hton8(sizeof(struct dhcp_option_addr));
+        struct dhcp_option_addr opt;
+        opt.addr = requested_ip_address;
+        memcpy(out, &oh, sizeof(oh));
+        memcpy(out + sizeof(oh), &opt, sizeof(opt));
+        out += sizeof(oh) + sizeof(opt);
+    }
+    
+    if (have_dhcp_server_identifier) {
+        // DHCP server identifier
+        oh.type = hton8(DHCP_OPTION_DHCP_SERVER_IDENTIFIER);
+        oh.len = hton8(sizeof(struct dhcp_option_dhcp_server_identifier));
+        struct dhcp_option_dhcp_server_identifier opt;
+        opt.id = dhcp_server_identifier;
+        memcpy(out, &oh, sizeof(oh));
+        memcpy(out + sizeof(oh), &opt, sizeof(opt));
+        out += sizeof(oh) + sizeof(opt);
+    }
+    
+    // maximum message size
+    {
+        oh.type = hton8(DHCP_OPTION_MAXIMUM_MESSAGE_SIZE);
+        oh.len = hton8(sizeof(struct dhcp_option_maximum_message_size));
+        struct dhcp_option_maximum_message_size opt;
+        opt.size = hton16(IP_UDP_HEADERS_SIZE + PacketRecvInterface_GetMTU(o->recv_if));
+        memcpy(out, &oh, sizeof(oh));
+        memcpy(out + sizeof(oh), &opt, sizeof(opt));
+        out += sizeof(oh) + sizeof(opt);
+    }
+    
+    // parameter request list
+    {
+        oh.type = hton8(DHCP_OPTION_PARAMETER_REQUEST_LIST);
+        oh.len = hton8(4);
+        uint8_t opt[4];
+        opt[0] = DHCP_OPTION_SUBNET_MASK;
+        opt[1] = DHCP_OPTION_ROUTER;
+        opt[2] = DHCP_OPTION_DOMAIN_NAME_SERVER;
+        opt[3] = DHCP_OPTION_IP_ADDRESS_LEASE_TIME;
+        memcpy(out, &oh, sizeof(oh));
+        memcpy(out + sizeof(oh), &opt, sizeof(opt));
+        out += sizeof(oh) + sizeof(opt);
+    }
+    
+    if (o->hostname) {
+        // host name
+        oh.type = hton8(DHCP_OPTION_HOST_NAME);
+        oh.len = hton8(strlen(o->hostname));
+        memcpy(out, &oh, sizeof(oh));
+        memcpy(out + sizeof(oh), o->hostname, strlen(o->hostname));
+        out += sizeof(oh) + strlen(o->hostname);
+    }
+    
+    if (o->vendorclassid) {
+        // vendor class identifier
+        oh.type = hton8(DHCP_OPTION_VENDOR_CLASS_IDENTIFIER);
+        oh.len = hton8(strlen(o->vendorclassid));
+        memcpy(out, &oh, sizeof(oh));
+        memcpy(out + sizeof(oh), o->vendorclassid, strlen(o->vendorclassid));
+        out += sizeof(oh) + strlen(o->vendorclassid);
+    }
+    
+    if (o->clientid) {
+        // client identifier
+        oh.type = hton8(DHCP_OPTION_CLIENT_IDENTIFIER);
+        oh.len = hton8(o->clientid_len);
+        memcpy(out, &oh, sizeof(oh));
+        memcpy(out + sizeof(oh), o->clientid, o->clientid_len);
+        out += sizeof(oh) + o->clientid_len;
+    }
+    
+    // end option
+    uint8_t end = 0xFF;
+    memcpy(out, &end, sizeof(end));
+    out += sizeof(end);
+    
+    // send it
+    PacketPassInterface_Sender_Send(o->send_if, (uint8_t *)o->send_buf, out - o->send_buf);
+    o->sending = 1;
+}
+
+static void send_handler_done (BDHCPClientCore *o)
+{
+    ASSERT(o->sending)
+    DebugObject_Access(&o->d_obj);
+    
+    o->sending = 0;
+}
+
+static void recv_handler_done (BDHCPClientCore *o, int data_len)
+{
+    ASSERT(data_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    // receive more packets
+    PacketRecvInterface_Receiver_Recv(o->recv_if, (uint8_t *)o->recv_buf);
+    
+    if (o->state == STATE_RESETTING) {
+        return;
+    }
+    
+    // check header
+    
+    if (data_len < sizeof(struct dhcp_header)) {
+        return;
+    }
+    
+    struct dhcp_header header;
+    memcpy(&header, o->recv_buf, sizeof(header));
+    
+    if (ntoh8(header.op) != DHCP_OP_BOOTREPLY) {
+        return;
+    }
+    
+    if (ntoh8(header.htype) != DHCP_HARDWARE_ADDRESS_TYPE_ETHERNET) {
+        return;
+    }
+    
+    if (ntoh8(header.hlen) != 6) {
+        return;
+    }
+    
+    if (header.xid != o->xid) {
+        return;
+    }
+    
+    if (memcmp(header.chaddr, o->client_mac_addr, sizeof(o->client_mac_addr))) {
+        return;
+    }
+    
+    if (ntoh32(header.magic) != DHCP_MAGIC) {
+        return;
+    }
+    
+    // parse and check options
+    
+    uint8_t *pos = (uint8_t *)o->recv_buf + sizeof(header);
+    int len = data_len - sizeof(header);
+    
+    int have_end = 0;
+    
+    int dhcp_message_type = -1;
+    
+    int have_dhcp_server_identifier = 0;
+    uint32_t dhcp_server_identifier = 0; // to remove warning
+    
+    int have_ip_address_lease_time = 0;
+    uint32_t ip_address_lease_time = 0; // to remove warning
+    
+    int have_subnet_mask = 0;
+    uint32_t subnet_mask = 0; // to remove warning
+    
+    int have_router = 0;
+    uint32_t router = 0; // to remove warning
+    
+    int domain_name_servers_count = 0;
+    uint32_t domain_name_servers[BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS];
+    
+    while (len > 0) {
+        // padding option ?
+        if (*pos == 0) {
+            pos++;
+            len--;
+            continue;
+        }
+        
+        if (have_end) {
+            return;
+        }
+        
+        // end option ?
+        if (*pos == 0xff) {
+            pos++;
+            len--;
+            have_end = 1;
+            continue;
+        }
+        
+        // check option header
+        if (len < sizeof(struct dhcp_option_header)) {
+            return;
+        }
+        struct dhcp_option_header opt;
+        memcpy(&opt, pos, sizeof(opt));
+        pos += sizeof(opt);
+        len -= sizeof(opt);
+        int opt_type = ntoh8(opt.type);
+        int opt_len = ntoh8(opt.len);
+        
+        // check option payload
+        if (opt_len > len) {
+            return;
+        }
+        uint8_t *optval = pos;
+        pos += opt_len;
+        len -= opt_len;
+        
+        switch (opt_type) {
+            case DHCP_OPTION_DHCP_MESSAGE_TYPE: {
+                if (opt_len != sizeof(struct dhcp_option_dhcp_message_type)) {
+                    return;
+                }
+                struct dhcp_option_dhcp_message_type val;
+                memcpy(&val, optval, sizeof(val));
+                
+                dhcp_message_type = ntoh8(val.type);
+            } break;
+            
+            case DHCP_OPTION_DHCP_SERVER_IDENTIFIER: {
+                if (opt_len != sizeof(struct dhcp_option_dhcp_server_identifier)) {
+                    return;
+                }
+                struct dhcp_option_dhcp_server_identifier val;
+                memcpy(&val, optval, sizeof(val));
+                
+                dhcp_server_identifier = val.id;
+                have_dhcp_server_identifier = 1;
+            } break;
+            
+            case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: {
+                if (opt_len != sizeof(struct dhcp_option_time)) {
+                    return;
+                }
+                struct dhcp_option_time val;
+                memcpy(&val, optval, sizeof(val));
+                
+                ip_address_lease_time = ntoh32(val.time);
+                have_ip_address_lease_time = 1;
+            } break;
+            
+            case DHCP_OPTION_SUBNET_MASK: {
+                if (opt_len != sizeof(struct dhcp_option_addr)) {
+                    return;
+                }
+                struct dhcp_option_addr val;
+                memcpy(&val, optval, sizeof(val));
+                
+                subnet_mask = val.addr;
+                have_subnet_mask = 1;
+            } break;
+            
+            case DHCP_OPTION_ROUTER: {
+                if (opt_len != sizeof(struct dhcp_option_addr)) {
+                    return;
+                }
+                struct dhcp_option_addr val;
+                memcpy(&val, optval, sizeof(val));
+                
+                router = val.addr;
+                have_router = 1;
+            } break;
+            
+            case DHCP_OPTION_DOMAIN_NAME_SERVER: {
+                if (opt_len % sizeof(struct dhcp_option_addr)) {
+                    return;
+                }
+                
+                int num_servers = opt_len / sizeof(struct dhcp_option_addr);
+                
+                int i;
+                for (i = 0; i < num_servers && i < BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS; i++) {
+                    struct dhcp_option_addr addr;
+                    memcpy(&addr, optval + i * sizeof(addr), sizeof(addr));
+                    domain_name_servers[i] = addr.addr;
+                }
+                
+                domain_name_servers_count = i;
+            } break;
+        }
+    }
+    
+    if (!have_end) {
+        return;
+    }
+    
+    if (dhcp_message_type == -1) {
+        return;
+    }
+    
+    if (dhcp_message_type != DHCP_MESSAGE_TYPE_OFFER && dhcp_message_type != DHCP_MESSAGE_TYPE_ACK && dhcp_message_type != DHCP_MESSAGE_TYPE_NAK) {
+        return;
+    }
+    
+    if (!have_dhcp_server_identifier) {
+        return;
+    }
+    
+    if (dhcp_message_type == DHCP_MESSAGE_TYPE_NAK) {
+        if (o->state != STATE_SENT_REQUEST && o->state != STATE_FINISHED && o->state != STATE_RENEWING) {
+            return;
+        }
+        
+        if (dhcp_server_identifier != o->offered.dhcp_server_identifier) {
+            return;
+        }
+        
+        if (o->state == STATE_SENT_REQUEST) {
+            BLog(BLOG_INFO, "received NAK (in sent request)");
+            
+            // stop request timer
+            BReactor_RemoveTimer(o->reactor, &o->request_timer);
+            
+            // start reset timer
+            BReactor_SetTimer(o->reactor, &o->reset_timer);
+            
+            // set state
+            o->state = STATE_RESETTING;
+        }
+        else if (o->state == STATE_FINISHED) {
+            BLog(BLOG_INFO, "received NAK (in finished)");
+            
+            // stop renew timer
+            BReactor_RemoveTimer(o->reactor, &o->renew_timer);
+            
+            // start reset timer
+            BReactor_SetTimer(o->reactor, &o->reset_timer);
+            
+            // set state
+            o->state = STATE_RESETTING;
+            
+            // report to user
+            report_down(o);
+            return;
+        }
+        else { // STATE_RENEWING
+            BLog(BLOG_INFO, "received NAK (in renewing)");
+            
+            // stop renew request timer
+            BReactor_RemoveTimer(o->reactor, &o->renew_request_timer);
+            
+            // stop lease timer
+            BReactor_RemoveTimer(o->reactor, &o->lease_timer);
+            
+            // start reset timer
+            BReactor_SetTimer(o->reactor, &o->reset_timer);
+            
+            // set state
+            o->state = STATE_RESETTING;
+            
+            // report to user
+            report_down(o);
+            return;
+        }
+        
+        return;
+    }
+    
+    if (ntoh32(header.yiaddr) == 0) {
+        return;
+    }
+    
+    if (!have_ip_address_lease_time) {
+        return;
+    }
+    
+    if (!have_subnet_mask) {
+        return;
+    }
+    
+    if (o->state == STATE_SENT_DISCOVER && dhcp_message_type == DHCP_MESSAGE_TYPE_OFFER) {
+        BLog(BLOG_INFO, "received OFFER");
+        
+        // remember offer
+        o->offered.yiaddr = header.yiaddr;
+        o->offered.dhcp_server_identifier = dhcp_server_identifier;
+        
+        // send request
+        send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 1, o->offered.dhcp_server_identifier);
+        
+        // stop reset timer
+        BReactor_RemoveTimer(o->reactor, &o->reset_timer);
+        
+        // start request timer
+        BReactor_SetTimer(o->reactor, &o->request_timer);
+        
+        // set state
+        o->state = STATE_SENT_REQUEST;
+        
+        // set request count
+        o->request_count = 1;
+    }
+    else if (o->state == STATE_SENT_REQUEST && dhcp_message_type == DHCP_MESSAGE_TYPE_ACK) {
+        if (header.yiaddr != o->offered.yiaddr) {
+            return;
+        }
+        
+        if (dhcp_server_identifier != o->offered.dhcp_server_identifier) {
+            return;
+        }
+        
+        BLog(BLOG_INFO, "received ACK (in sent request)");
+        
+        // remember stuff
+        o->acked.ip_address_lease_time = ip_address_lease_time;
+        o->acked.subnet_mask = subnet_mask;
+        o->acked.have_router = have_router;
+        if (have_router) {
+            o->acked.router = router;
+        }
+        o->acked.domain_name_servers_count = domain_name_servers_count;
+        memcpy(o->acked.domain_name_servers, domain_name_servers, domain_name_servers_count * sizeof(uint32_t));
+        o->func_getsendermac(o->user, o->acked.server_mac);
+        
+        // stop request timer
+        BReactor_RemoveTimer(o->reactor, &o->request_timer);
+        
+        // start renew timer
+        BReactor_SetTimerAfter(o->reactor, &o->renew_timer, RENEW_TIMEOUT(o->acked.ip_address_lease_time));
+        
+        // set state
+        o->state = STATE_FINISHED;
+        
+        // report to user
+        report_up(o);
+        return;
+    }
+    else if (o->state == STATE_RENEWING && dhcp_message_type == DHCP_MESSAGE_TYPE_ACK) {
+        if (header.yiaddr != o->offered.yiaddr) {
+            return;
+        }
+        
+        if (dhcp_server_identifier != o->offered.dhcp_server_identifier) {
+            return;
+        }
+        
+        // TODO: check parameters?
+        
+        BLog(BLOG_INFO, "received ACK (in renewing)");
+        
+        // remember stuff
+        o->acked.ip_address_lease_time = ip_address_lease_time;
+        
+        // stop renew request timer
+        BReactor_RemoveTimer(o->reactor, &o->renew_request_timer);
+        
+        // stop lease timer
+        BReactor_RemoveTimer(o->reactor, &o->lease_timer);
+        
+        // start renew timer
+        BReactor_SetTimerAfter(o->reactor, &o->renew_timer, RENEW_TIMEOUT(o->acked.ip_address_lease_time));
+        
+        // set state
+        o->state = STATE_FINISHED;
+    }
+}
+
+static void start_process (BDHCPClientCore *o, int force_new_xid)
+{
+    if (force_new_xid || o->xid_reuse_counter == XID_REUSE_MAX) {
+        // generate xid
+        if (!BRandom2_GenBytes(o->random2, &o->xid, sizeof(o->xid))) {
+            BLog(BLOG_ERROR, "BRandom2_GenBytes failed");
+            o->xid = UINT32_C(3416960072);
+        }
+        
+        // reset counter
+        o->xid_reuse_counter = 0;
+    }
+    
+    // increment counter
+    o->xid_reuse_counter++;
+    
+    // send discover
+    send_message(o, DHCP_MESSAGE_TYPE_DISCOVER, o->xid, 0, 0, 0, 0);
+    
+    // set timer
+    BReactor_SetTimer(o->reactor, &o->reset_timer);
+    
+    // set state
+    o->state = STATE_SENT_DISCOVER;
+}
+
+static void reset_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_RESETTING || o->state == STATE_SENT_DISCOVER)
+    DebugObject_Access(&o->d_obj);
+    
+    BLog(BLOG_INFO, "reset timer");
+    
+    start_process(o, (o->state == STATE_RESETTING));
+}
+
+static void request_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_SENT_REQUEST)
+    ASSERT(o->request_count >= 1)
+    ASSERT(o->request_count <= MAX_REQUESTS)
+    DebugObject_Access(&o->d_obj);
+    
+    // if we have sent enough requests, start again
+    if (o->request_count == MAX_REQUESTS) {
+        BLog(BLOG_INFO, "request timer, aborting");
+        
+        start_process(o, 0);
+        return;
+    }
+    
+    BLog(BLOG_INFO, "request timer, retrying");
+    
+    // send request
+    send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 1, o->offered.dhcp_server_identifier);
+    
+    // start request timer
+    BReactor_SetTimer(o->reactor, &o->request_timer);
+    
+    // increment request count
+    o->request_count++;
+}
+
+static void renew_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_FINISHED)
+    DebugObject_Access(&o->d_obj);
+    
+    BLog(BLOG_INFO, "renew timer");
+    
+    // send request
+    send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 0, 0);
+    
+    // start renew request timer
+    BReactor_SetTimer(o->reactor, &o->renew_request_timer);
+    
+    // start lease timer
+    BReactor_SetTimerAfter(o->reactor, &o->lease_timer, LEASE_TIMEOUT(o->acked.ip_address_lease_time));
+    
+    // set state
+    o->state = STATE_RENEWING;
+}
+
+static void renew_request_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    BLog(BLOG_INFO, "renew request timer");
+    
+    // send request
+    send_message(o, DHCP_MESSAGE_TYPE_REQUEST, o->xid, 1, o->offered.yiaddr, 0, 0);
+    
+    // start renew request timer
+    BReactor_SetTimer(o->reactor, &o->renew_request_timer);
+}
+
+static void lease_timer_handler (BDHCPClientCore *o)
+{
+    ASSERT(o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    BLog(BLOG_INFO, "lease timer");
+    
+    // stop renew request timer
+    BReactor_RemoveTimer(o->reactor, &o->renew_request_timer);
+    
+    // start again now
+    start_process(o, 1);
+    
+    // report to user
+    report_down(o);
+    return;
+}
+
+static bsize_t maybe_len (const char *str)
+{
+    return bsize_fromsize(str ? strlen(str) : 0);
+}
+
+int BDHCPClientCore_Init (BDHCPClientCore *o, PacketPassInterface *send_if, PacketRecvInterface *recv_if,
+                          uint8_t *client_mac_addr, struct BDHCPClientCore_opts opts, BReactor *reactor,
+                          BRandom2 *random2, void *user,
+                          BDHCPClientCore_func_getsendermac func_getsendermac,
+                          BDHCPClientCore_handler handler)
+{
+    ASSERT(PacketPassInterface_GetMTU(send_if) == PacketRecvInterface_GetMTU(recv_if))
+    ASSERT(PacketPassInterface_GetMTU(send_if) >= 576 - IP_UDP_HEADERS_SIZE)
+    ASSERT(func_getsendermac)
+    ASSERT(handler)
+    
+    // init arguments
+    o->send_if = send_if;
+    o->recv_if = recv_if;
+    memcpy(o->client_mac_addr, client_mac_addr, sizeof(o->client_mac_addr));
+    o->reactor = reactor;
+    o->random2 = random2;
+    o->user = user;
+    o->func_getsendermac = func_getsendermac;
+    o->handler = handler;
+    
+    o->hostname = NULL;
+    o->vendorclassid = NULL;
+    o->clientid = NULL;
+    o->clientid_len = 0;
+    
+    // copy options
+    if (opts.hostname && !(o->hostname = strdup(opts.hostname))) {
+        BLog(BLOG_ERROR, "strdup failed");
+        goto fail0;
+    }
+    if (opts.vendorclassid && !(o->vendorclassid = strdup(opts.vendorclassid))) {
+        BLog(BLOG_ERROR, "strdup failed");
+        goto fail0;
+    }
+    if (opts.clientid) {
+        if (!(o->clientid = BAlloc(opts.clientid_len))) {
+            BLog(BLOG_ERROR, "BAlloc failed");
+            goto fail0;
+        }
+        memcpy(o->clientid, opts.clientid, opts.clientid_len);
+        o->clientid_len = opts.clientid_len;
+    }
+    
+    // make sure options aren't too long
+    bsize_t opts_size = bsize_add(maybe_len(o->hostname), bsize_add(maybe_len(o->vendorclassid), bsize_fromsize(o->clientid_len)));
+    if (opts_size.is_overflow || opts_size.value > 100) {
+        BLog(BLOG_ERROR, "options too long together");
+        goto fail0;
+    }
+    if (o->hostname && strlen(o->hostname) > 255) {
+        BLog(BLOG_ERROR, "hostname too long");
+        goto fail0;
+    }
+    if (o->vendorclassid && strlen(o->vendorclassid) > 255) {
+        BLog(BLOG_ERROR, "vendorclassid too long");
+        goto fail0;
+    }
+    if (o->clientid && o->clientid_len > 255) {
+        BLog(BLOG_ERROR, "clientid too long");
+        goto fail0;
+    }
+    
+    // allocate buffers
+    if (!(o->send_buf = BAlloc(PacketPassInterface_GetMTU(send_if)))) {
+        BLog(BLOG_ERROR, "BAlloc send buf failed");
+        goto fail0;
+    }
+    if (!(o->recv_buf = BAlloc(PacketRecvInterface_GetMTU(recv_if)))) {
+        BLog(BLOG_ERROR, "BAlloc recv buf failed");
+        goto fail1;
+    }
+    
+    // init send interface
+    PacketPassInterface_Sender_Init(o->send_if, (PacketPassInterface_handler_done)send_handler_done, o);
+    
+    // init receive interface
+    PacketRecvInterface_Receiver_Init(o->recv_if, (PacketRecvInterface_handler_done)recv_handler_done, o);
+    
+    // set not sending
+    o->sending = 0;
+    
+    // init timers
+    BTimer_Init(&o->reset_timer, RESET_TIMEOUT, (BTimer_handler)reset_timer_handler, o);
+    BTimer_Init(&o->request_timer, REQUEST_TIMEOUT, (BTimer_handler)request_timer_handler, o);
+    BTimer_Init(&o->renew_timer, 0, (BTimer_handler)renew_timer_handler, o);
+    BTimer_Init(&o->renew_request_timer, RENEW_REQUEST_TIMEOUT, (BTimer_handler)renew_request_timer_handler, o);
+    BTimer_Init(&o->lease_timer, 0, (BTimer_handler)lease_timer_handler, o);
+    
+    // start receving
+    PacketRecvInterface_Receiver_Recv(o->recv_if, (uint8_t *)o->recv_buf);
+    
+    // start
+    start_process(o, 1);
+    
+    DebugObject_Init(&o->d_obj);
+    
+    return 1;
+    
+fail1:
+    BFree(o->send_buf);
+fail0:
+    BFree(o->clientid);
+    free(o->vendorclassid);
+    free(o->hostname);
+    return 0;
+}
+
+void BDHCPClientCore_Free (BDHCPClientCore *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free timers
+    BReactor_RemoveTimer(o->reactor, &o->lease_timer);
+    BReactor_RemoveTimer(o->reactor, &o->renew_request_timer);
+    BReactor_RemoveTimer(o->reactor, &o->renew_timer);
+    BReactor_RemoveTimer(o->reactor, &o->request_timer);
+    BReactor_RemoveTimer(o->reactor, &o->reset_timer);
+    
+    // free buffers
+    BFree(o->recv_buf);
+    BFree(o->send_buf);
+    
+    // free options
+    BFree(o->clientid);
+    free(o->vendorclassid);
+    free(o->hostname);
+}
+
+void BDHCPClientCore_GetClientIP (BDHCPClientCore *o, uint32_t *out_ip)
+{
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    *out_ip = o->offered.yiaddr;
+}
+
+void BDHCPClientCore_GetClientMask (BDHCPClientCore *o, uint32_t *out_mask)
+{
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    *out_mask = o->acked.subnet_mask;
+}
+
+int BDHCPClientCore_GetRouter (BDHCPClientCore *o, uint32_t *out_router)
+{
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    if (!o->acked.have_router) {
+        return 0;
+    }
+    
+    *out_router = o->acked.router;
+    return 1;
+}
+
+int BDHCPClientCore_GetDNS (BDHCPClientCore *o, uint32_t *out_dns_servers, size_t max_dns_servers)
+{
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    DebugObject_Access(&o->d_obj);
+    
+    int num_return = bmin_int(o->acked.domain_name_servers_count, max_dns_servers);
+    
+    memcpy(out_dns_servers, o->acked.domain_name_servers, num_return * sizeof(uint32_t));
+    return num_return;
+}
+
+void BDHCPClientCore_GetServerMAC (BDHCPClientCore *o, uint8_t *out_mac)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == STATE_FINISHED || o->state == STATE_RENEWING)
+    
+    memcpy(out_mac, o->acked.server_mac, 6);
+}
diff --git a/external/badvpn_dns/dhcpclient/BDHCPClientCore.h b/external/badvpn_dns/dhcpclient/BDHCPClientCore.h
new file mode 100644
index 0000000..98cda36
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/BDHCPClientCore.h
@@ -0,0 +1,114 @@
+/**
+ * @file BDHCPClientCore.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * DHCP client, excluding system-dependent details.
+ */
+
+#ifndef BADVPN_DHCPCLIENT_BDHCPCLIENTCORE_H
+#define BADVPN_DHCPCLIENT_BDHCPCLIENTCORE_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <system/BReactor.h>
+#include <base/DebugObject.h>
+#include <random/BRandom2.h>
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+
+#define BDHCPCLIENTCORE_EVENT_UP 1
+#define BDHCPCLIENTCORE_EVENT_DOWN 2
+
+#define BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS 16
+
+typedef void (*BDHCPClientCore_func_getsendermac) (void *user, uint8_t *out_mac);
+typedef void (*BDHCPClientCore_handler) (void *user, int event);
+
+struct BDHCPClientCore_opts {
+    const char *hostname;
+    const char *vendorclassid;
+    const uint8_t *clientid;
+    size_t clientid_len;
+};
+
+typedef struct {
+    PacketPassInterface *send_if;
+    PacketRecvInterface *recv_if;
+    uint8_t client_mac_addr[6];
+    BReactor *reactor;
+    BRandom2 *random2;
+    void *user;
+    BDHCPClientCore_func_getsendermac func_getsendermac;
+    BDHCPClientCore_handler handler;
+    char *hostname;
+    char *vendorclassid;
+    uint8_t *clientid;
+    size_t clientid_len;
+    char *send_buf;
+    char *recv_buf;
+    int sending;
+    BTimer reset_timer;
+    BTimer request_timer;
+    BTimer renew_timer;
+    BTimer renew_request_timer;
+    BTimer lease_timer;
+    int state;
+    int request_count;
+    uint32_t xid;
+    int xid_reuse_counter;
+    struct {
+        uint32_t yiaddr;
+        uint32_t dhcp_server_identifier;
+    } offered;
+    struct {
+        uint32_t ip_address_lease_time;
+        uint32_t subnet_mask;
+        int have_router;
+        uint32_t router;
+        int domain_name_servers_count;
+        uint32_t domain_name_servers[BDHCPCLIENTCORE_MAX_DOMAIN_NAME_SERVERS];
+        uint8_t server_mac[6];
+    } acked;
+    DebugObject d_obj;
+} BDHCPClientCore;
+
+int BDHCPClientCore_Init (BDHCPClientCore *o, PacketPassInterface *send_if, PacketRecvInterface *recv_if,
+                          uint8_t *client_mac_addr, struct BDHCPClientCore_opts opts, BReactor *reactor,
+                          BRandom2 *random2, void *user,
+                          BDHCPClientCore_func_getsendermac func_getsendermac,
+                          BDHCPClientCore_handler handler);
+void BDHCPClientCore_Free (BDHCPClientCore *o);
+void BDHCPClientCore_GetClientIP (BDHCPClientCore *o, uint32_t *out_ip);
+void BDHCPClientCore_GetClientMask (BDHCPClientCore *o, uint32_t *out_mask);
+int BDHCPClientCore_GetRouter (BDHCPClientCore *o, uint32_t *out_router);
+int BDHCPClientCore_GetDNS (BDHCPClientCore *o, uint32_t *out_dns_servers, size_t max_dns_servers);
+void BDHCPClientCore_GetServerMAC (BDHCPClientCore *o, uint8_t *out_mac);
+
+#endif
diff --git a/external/badvpn_dns/dhcpclient/CMakeLists.txt b/external/badvpn_dns/dhcpclient/CMakeLists.txt
new file mode 100644
index 0000000..5fd1300
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/CMakeLists.txt
@@ -0,0 +1,10 @@
+badvpn_add_library(dhcpclientcore "system;flow;flowextra;badvpn_random" "" BDHCPClientCore.c)
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    set(DHCPCLIENT_SOURCES
+        BDHCPClient.c
+        DHCPIpUdpEncoder.c
+        DHCPIpUdpDecoder.c
+    )
+    badvpn_add_library(dhcpclient "system;flow;dhcpclientcore" "" "${DHCPCLIENT_SOURCES}")
+endif ()
diff --git a/external/badvpn_dns/dhcpclient/DHCPIpUdpDecoder.c b/external/badvpn_dns/dhcpclient/DHCPIpUdpDecoder.c
new file mode 100644
index 0000000..1d1c7a7
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/DHCPIpUdpDecoder.c
@@ -0,0 +1,137 @@
+/**
+ * @file DHCPIpUdpDecoder.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <limits.h>
+#include <string.h>
+
+#include <misc/ipv4_proto.h>
+#include <misc/udp_proto.h>
+#include <misc/byteorder.h>
+
+#include <dhcpclient/DHCPIpUdpDecoder.h>
+
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+#define IPUDP_HEADER_SIZE (sizeof(struct ipv4_header) + sizeof(struct udp_header))
+
+static void input_handler_send (DHCPIpUdpDecoder *o, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    struct ipv4_header iph;
+    uint8_t *pl;
+    int pl_len;
+    
+    if (!ipv4_check(data, data_len, &iph, &pl, &pl_len)) {
+        goto fail;
+    }
+    
+    if (ntoh8(iph.protocol) != IPV4_PROTOCOL_UDP) {
+        goto fail;
+    }
+    
+    if (pl_len < sizeof(struct udp_header)) {
+        goto fail;
+    }
+    struct udp_header udph;
+    memcpy(&udph, pl, sizeof(udph));
+    
+    if (ntoh16(udph.source_port) != DHCP_SERVER_PORT) {
+        goto fail;
+    }
+    
+    if (ntoh16(udph.dest_port) != DHCP_CLIENT_PORT) {
+        goto fail;
+    }
+    
+    int udph_length = ntoh16(udph.length);
+    if (udph_length < sizeof(udph)) {
+        goto fail;
+    }
+    if (udph_length > data_len - (pl - data)) {
+        goto fail;
+    }
+    
+    if (ntoh16(udph.checksum) != 0) {
+        uint16_t checksum_in_packet = udph.checksum;
+        udph.checksum = 0;
+        uint16_t checksum_computed = udp_checksum(&udph, pl + sizeof(udph), udph_length - sizeof(udph), iph.source_address, iph.destination_address);
+        if (checksum_in_packet != checksum_computed) {
+            goto fail;
+        }
+    }
+    
+    // pass payload to output
+    PacketPassInterface_Sender_Send(o->output, pl + sizeof(udph), udph_length - sizeof(udph));
+    
+    return;
+    
+fail:
+    PacketPassInterface_Done(&o->input);
+}
+
+static void output_handler_done (DHCPIpUdpDecoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    PacketPassInterface_Done(&o->input);
+}
+
+void DHCPIpUdpDecoder_Init (DHCPIpUdpDecoder *o, PacketPassInterface *output, BPendingGroup *pg)
+{
+    ASSERT(PacketPassInterface_GetMTU(output) <= INT_MAX - IPUDP_HEADER_SIZE)
+    
+    // init arguments
+    o->output = output;
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // init input
+    PacketPassInterface_Init(&o->input, IPUDP_HEADER_SIZE + PacketPassInterface_GetMTU(o->output), (PacketPassInterface_handler_send)input_handler_send, o, pg);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void DHCPIpUdpDecoder_Free (DHCPIpUdpDecoder *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+PacketPassInterface * DHCPIpUdpDecoder_GetInput (DHCPIpUdpDecoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
diff --git a/external/badvpn_dns/dhcpclient/DHCPIpUdpDecoder.h b/external/badvpn_dns/dhcpclient/DHCPIpUdpDecoder.h
new file mode 100644
index 0000000..ce92138
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/DHCPIpUdpDecoder.h
@@ -0,0 +1,49 @@
+/**
+ * @file DHCPIpUdpDecoder.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_DHCPCLIENT_DHCPIPUDPDECODER_H
+#define BADVPN_DHCPCLIENT_DHCPIPUDPDECODER_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+typedef struct {
+    PacketPassInterface *output;
+    PacketPassInterface input;
+    uint8_t *data;
+    DebugObject d_obj;
+} DHCPIpUdpDecoder;
+
+void DHCPIpUdpDecoder_Init (DHCPIpUdpDecoder *o, PacketPassInterface *output, BPendingGroup *pg);
+void DHCPIpUdpDecoder_Free (DHCPIpUdpDecoder *o);
+PacketPassInterface * DHCPIpUdpDecoder_GetInput (DHCPIpUdpDecoder *o);
+
+#endif
diff --git a/external/badvpn_dns/dhcpclient/DHCPIpUdpEncoder.c b/external/badvpn_dns/dhcpclient/DHCPIpUdpEncoder.c
new file mode 100644
index 0000000..da6645c
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/DHCPIpUdpEncoder.c
@@ -0,0 +1,119 @@
+/**
+ * @file DHCPIpUdpEncoder.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <limits.h>
+#include <string.h>
+#include <stddef.h>
+
+#include <misc/ipv4_proto.h>
+#include <misc/udp_proto.h>
+#include <misc/byteorder.h>
+
+#include <dhcpclient/DHCPIpUdpEncoder.h>
+
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+
+#define IPUDP_HEADER_SIZE (sizeof(struct ipv4_header) + sizeof(struct udp_header))
+
+static void output_handler_recv (DHCPIpUdpEncoder *o, uint8_t *data)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // remember output packet
+    o->data = data;
+    
+    // receive payload
+    PacketRecvInterface_Receiver_Recv(o->input, o->data + IPUDP_HEADER_SIZE);
+}
+
+static void input_handler_done (DHCPIpUdpEncoder *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    // build IP header
+    struct ipv4_header iph;
+    iph.version4_ihl4 = IPV4_MAKE_VERSION_IHL(sizeof(iph));
+    iph.ds = hton8(0);
+    iph.total_length = hton16(IPUDP_HEADER_SIZE + data_len);
+    iph.identification = hton16(0);
+    iph.flags3_fragmentoffset13 = hton16(0);
+    iph.ttl = hton8(64);
+    iph.protocol = hton8(IPV4_PROTOCOL_UDP);
+    iph.checksum = hton16(0);
+    iph.source_address = hton32(0x00000000);
+    iph.destination_address = hton32(0xFFFFFFFF);
+    iph.checksum = ipv4_checksum(&iph, NULL, 0);
+    
+    // write UDP header
+    struct udp_header udph;
+    udph.source_port = hton16(DHCP_CLIENT_PORT);
+    udph.dest_port = hton16(DHCP_SERVER_PORT);
+    udph.length = hton16(sizeof(udph) + data_len);
+    udph.checksum = hton16(0);
+    udph.checksum = udp_checksum(&udph, o->data + IPUDP_HEADER_SIZE, data_len, iph.source_address, iph.destination_address);
+    
+    // write header
+    memcpy(o->data, &iph, sizeof(iph));
+    memcpy(o->data + sizeof(iph), &udph, sizeof(udph));
+    
+    // finish packet
+    PacketRecvInterface_Done(&o->output, IPUDP_HEADER_SIZE + data_len);
+}
+
+void DHCPIpUdpEncoder_Init (DHCPIpUdpEncoder *o, PacketRecvInterface *input, BPendingGroup *pg)
+{
+    ASSERT(PacketRecvInterface_GetMTU(input) <= INT_MAX - IPUDP_HEADER_SIZE)
+    
+    // init arguments
+    o->input = input;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(o->input, (PacketRecvInterface_handler_done)input_handler_done, o);
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, IPUDP_HEADER_SIZE + PacketRecvInterface_GetMTU(o->input), (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void DHCPIpUdpEncoder_Free (DHCPIpUdpEncoder *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free output
+    PacketRecvInterface_Free(&o->output);
+}
+
+PacketRecvInterface * DHCPIpUdpEncoder_GetOutput (DHCPIpUdpEncoder *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
diff --git a/external/badvpn_dns/dhcpclient/DHCPIpUdpEncoder.h b/external/badvpn_dns/dhcpclient/DHCPIpUdpEncoder.h
new file mode 100644
index 0000000..d3e0ac0
--- /dev/null
+++ b/external/badvpn_dns/dhcpclient/DHCPIpUdpEncoder.h
@@ -0,0 +1,49 @@
+/**
+ * @file DHCPIpUdpEncoder.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_DHCPCLIENT_DHCPIPUDPENCODER_H
+#define BADVPN_DHCPCLIENT_DHCPIPUDPENCODER_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+typedef struct {
+    PacketRecvInterface *input;
+    PacketRecvInterface output;
+    uint8_t *data;
+    DebugObject d_obj;
+} DHCPIpUdpEncoder;
+
+void DHCPIpUdpEncoder_Init (DHCPIpUdpEncoder *o, PacketRecvInterface *input, BPendingGroup *pg);
+void DHCPIpUdpEncoder_Free (DHCPIpUdpEncoder *o);
+PacketRecvInterface * DHCPIpUdpEncoder_GetOutput (DHCPIpUdpEncoder *o);
+
+#endif
diff --git a/external/badvpn_dns/dostest/CMakeLists.txt b/external/badvpn_dns/dostest/CMakeLists.txt
new file mode 100644
index 0000000..8d3b742
--- /dev/null
+++ b/external/badvpn_dns/dostest/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_executable(dostest-server
+    dostest-server.c
+    StreamBuffer.c
+)
+target_link_libraries(dostest-server base system)
+
+add_executable(dostest-attacker
+    dostest-attacker.c
+)
+target_link_libraries(dostest-attacker base system)
diff --git a/external/badvpn_dns/dostest/StreamBuffer.c b/external/badvpn_dns/dostest/StreamBuffer.c
new file mode 100644
index 0000000..d439127
--- /dev/null
+++ b/external/badvpn_dns/dostest/StreamBuffer.c
@@ -0,0 +1,147 @@
+/**
+ * @file StreamBuffer.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <misc/balloc.h>
+#include <misc/minmax.h>
+
+#include "StreamBuffer.h"
+
+// called when receive operation is complete
+static void input_handler_done (void *vo, int data_len)
+{
+    StreamBuffer *o = (StreamBuffer *)vo;
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->buf_size - (o->buf_start + o->buf_used))
+    
+    // remember if buffer was empty
+    int was_empty = (o->buf_used == 0);
+    
+    // increment buf_used by the amount that was received
+    o->buf_used += data_len;
+    
+    // start another receive operation unless buffer is full
+    if (o->buf_used < o->buf_size - o->buf_start) {
+        int end = o->buf_start + o->buf_used;
+        StreamRecvInterface_Receiver_Recv(o->input, o->buf + end, o->buf_size - end);
+    }
+    else if (o->buf_used < o->buf_size) {
+        // wrap around
+        StreamRecvInterface_Receiver_Recv(o->input, o->buf, o->buf_start);
+    }
+    
+    // if buffer was empty before, start send operation
+    if (was_empty) {
+        StreamPassInterface_Sender_Send(o->output, o->buf + o->buf_start, o->buf_used);
+    }
+}
+
+// called when send operation is complete
+static void output_handler_done (void *vo, int data_len)
+{
+    StreamBuffer *o = (StreamBuffer *)vo;
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->buf_used)
+    ASSERT(data_len <= o->buf_size - o->buf_start)
+    
+    // remember if buffer was full
+    int was_full = (o->buf_used == o->buf_size);
+    
+    // increment buf_start and decrement buf_used by the
+    // amount that was sent
+    o->buf_start += data_len;
+    o->buf_used -= data_len;
+    
+    // wrap around buf_start
+    if (o->buf_start == o->buf_size) {
+        o->buf_start = 0;
+    }
+    
+    // start receive operation if buffer was full
+    if (was_full) {
+        int end;
+        int avail;
+        if (o->buf_used >= o->buf_size - o->buf_start) {
+            end = o->buf_used - (o->buf_size - o->buf_start);
+            avail = o->buf_start - end;
+        } else {
+            end = o->buf_start + o->buf_used;
+            avail = o->buf_size - end;
+        }
+        StreamRecvInterface_Receiver_Recv(o->input, o->buf + end, avail);
+    }
+    
+    // start another receive send unless buffer is empty
+    if (o->buf_used > 0) {
+        int to_send = bmin_int(o->buf_used, o->buf_size - o->buf_start);
+        StreamPassInterface_Sender_Send(o->output, o->buf + o->buf_start, to_send);
+    }
+}
+
+int StreamBuffer_Init (StreamBuffer *o, int buf_size, StreamRecvInterface *input, StreamPassInterface *output)
+{
+    ASSERT(buf_size > 0)
+    ASSERT(input)
+    ASSERT(output)
+    
+    // remember arguments
+    o->buf_size = buf_size;
+    o->input = input;
+    o->output = output;
+    
+    // allocate buffer memory
+    o->buf = (uint8_t *)BAllocSize(bsize_fromint(o->buf_size));
+    if (!o->buf) {
+        goto fail0;
+    }
+    
+    // set initial buffer state
+    o->buf_start = 0;
+    o->buf_used = 0;
+    
+    // set receive and send done callbacks
+    StreamRecvInterface_Receiver_Init(o->input, input_handler_done, o);
+    StreamPassInterface_Sender_Init(o->output, output_handler_done, o);
+    
+    // start receive operation
+    StreamRecvInterface_Receiver_Recv(o->input, o->buf, o->buf_size);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void StreamBuffer_Free (StreamBuffer *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free buffer memory
+    BFree(o->buf);
+}
diff --git a/external/badvpn_dns/dostest/StreamBuffer.h b/external/badvpn_dns/dostest/StreamBuffer.h
new file mode 100644
index 0000000..dd441f5
--- /dev/null
+++ b/external/badvpn_dns/dostest/StreamBuffer.h
@@ -0,0 +1,70 @@
+/**
+ * @file StreamBuffer.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_STREAMBUFFER_H
+#define BADVPN_STREAMBUFFER_H
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <flow/StreamRecvInterface.h>
+#include <flow/StreamPassInterface.h>
+
+/**
+ * Buffer object which reads data from a \link StreamRecvInterface and writes
+ * it to a \link StreamPassInterface.
+ */
+typedef struct {
+    int buf_size;
+    StreamRecvInterface *input;
+    StreamPassInterface *output;
+    uint8_t *buf;
+    int buf_start;
+    int buf_used;
+    DebugObject d_obj;
+} StreamBuffer;
+
+/**
+ * Initializes the buffer object.
+ * 
+ * @param o object to initialize
+ * @param buf_size size of the buffer. Must be >0.
+ * @param input input interface
+ * @param outout output interface
+ * @return 1 on success, 0 on failure
+ */
+int StreamBuffer_Init (StreamBuffer *o, int buf_size, StreamRecvInterface *input, StreamPassInterface *output) WARN_UNUSED;
+
+/**
+ * Frees the buffer object.
+ * 
+ * @param o object to free
+ */
+void StreamBuffer_Free (StreamBuffer *o);
+
+#endif
diff --git a/external/badvpn_dns/dostest/dostest-attacker.c b/external/badvpn_dns/dostest/dostest-attacker.c
new file mode 100644
index 0000000..723dadd
--- /dev/null
+++ b/external/badvpn_dns/dostest/dostest-attacker.c
@@ -0,0 +1,512 @@
+/**
+ * @file dostest-attacker.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/version.h>
+#include <misc/offset.h>
+#include <misc/open_standard_streams.h>
+#include <misc/balloc.h>
+#include <misc/loglevel.h>
+#include <misc/minmax.h>
+#include <structure/LinkedList1.h>
+#include <base/BLog.h>
+#include <system/BAddr.h>
+#include <system/BReactor.h>
+#include <system/BNetwork.h>
+#include <system/BConnection.h>
+#include <system/BSignal.h>
+
+#include <generated/blog_channel_dostest_attacker.h>
+
+#define PROGRAM_NAME "dostest-attacker"
+
+// connection structure
+struct connection {
+    int connected;
+    BConnector connector;
+    BConnection con;
+    StreamRecvInterface *recv_if;
+    uint8_t buf[512];
+    LinkedList1Node connections_list_node;
+};
+
+// command-line options
+static struct {
+    int help;
+    int version;
+    char *connect_addr;
+    int max_connections;
+    int max_connecting;
+    int loglevel;
+    int loglevels[BLOG_NUM_CHANNELS];
+} options;
+
+// connect address
+static BAddr connect_addr;
+
+// reactor
+static BReactor reactor;
+
+// connections
+static LinkedList1 connections_list;
+static int num_connections;
+static int num_connecting;
+
+// timer for scheduling creation of more connections
+static BTimer make_connections_timer;
+
+static void print_help (const char *name);
+static void print_version (void);
+static int parse_arguments (int argc, char *argv[]);
+static int process_arguments (void);
+static void signal_handler (void *unused);
+static int connection_new (void);
+static void connection_free (struct connection *conn);
+static void connection_logfunc (struct connection *conn);
+static void connection_log (struct connection *conn, int level, const char *fmt, ...);
+static void connection_connector_handler (struct connection *conn, int is_error);
+static void connection_connection_handler (struct connection *conn, int event);
+static void connection_recv_handler_done (struct connection *conn, int data_len);
+static void make_connections_timer_handler (void *unused);
+
+int main (int argc, char **argv)
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    // open standard streams
+    open_standard_streams();
+    
+    // parse command-line arguments
+    if (!parse_arguments(argc, argv)) {
+        fprintf(stderr, "Failed to parse arguments\n");
+        print_help(argv[0]);
+        goto fail0;
+    }
+    
+    // handle --help and --version
+    if (options.help) {
+        print_version();
+        print_help(argv[0]);
+        return 0;
+    }
+    if (options.version) {
+        print_version();
+        return 0;
+    }
+    
+    // init loger
+    BLog_InitStderr();
+    
+    // configure logger channels
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        if (options.loglevels[i] >= 0) {
+            BLog_SetChannelLoglevel(i, options.loglevels[i]);
+        }
+        else if (options.loglevel >= 0) {
+            BLog_SetChannelLoglevel(i, options.loglevel);
+        }
+    }
+    
+    BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION);
+    
+    // initialize network
+    if (!BNetwork_GlobalInit()) {
+        BLog(BLOG_ERROR, "BNetwork_GlobalInit failed");
+        goto fail1;
+    }
+    
+    // process arguments
+    if (!process_arguments()) {
+        BLog(BLOG_ERROR, "Failed to process arguments");
+        goto fail1;
+    }
+    
+    // init time
+    BTime_Init();
+    
+    // init reactor
+    if (!BReactor_Init(&reactor)) {
+        BLog(BLOG_ERROR, "BReactor_Init failed");
+        goto fail1;
+    }
+    
+    // setup signal handler
+    if (!BSignal_Init(&reactor, signal_handler, NULL)) {
+        BLog(BLOG_ERROR, "BSignal_Init failed");
+        goto fail2;
+    }
+    
+    // init connections list
+    LinkedList1_Init(&connections_list);
+    num_connections = 0;
+    num_connecting = 0;
+    
+    // init make connections timer
+    BTimer_Init(&make_connections_timer, 0, make_connections_timer_handler, NULL);
+    BReactor_SetTimer(&reactor, &make_connections_timer);
+    
+    // enter event loop
+    BLog(BLOG_NOTICE, "entering event loop");
+    BReactor_Exec(&reactor);
+    
+    // free connections
+    while (!LinkedList1_IsEmpty(&connections_list)) {
+        struct connection *conn = UPPER_OBJECT(LinkedList1_GetFirst(&connections_list), struct connection, connections_list_node);
+        connection_free(conn);
+    }
+    // free make connections timer
+    BReactor_RemoveTimer(&reactor, &make_connections_timer);
+    // free signal
+    BSignal_Finish();
+fail2:
+    // free reactor
+    BReactor_Free(&reactor);
+fail1:
+    // free logger
+    BLog(BLOG_NOTICE, "exiting");
+    BLog_Free();
+fail0:
+    // finish debug objects
+    DebugObjectGlobal_Finish();
+    
+    return 1;
+}
+
+void print_help (const char *name)
+{
+    printf(
+        "Usage:\n"
+        "    %s\n"
+        "        [--help]\n"
+        "        [--version]\n"
+        "        --connect-addr <addr>\n"
+        "        --max-connections <number>\n"
+        "        --max-connecting <number>\n"
+        "        [--loglevel <0-5/none/error/warning/notice/info/debug>]\n"
+        "        [--channel-loglevel <channel-name> <0-5/none/error/warning/notice/info/debug>] ...\n"
+        "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n",
+        name
+    );
+}
+
+void print_version (void)
+{
+    printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n");
+}
+
+int parse_arguments (int argc, char *argv[])
+{
+    options.help = 0;
+    options.version = 0;
+    options.connect_addr = NULL;
+    options.max_connections = -1;
+    options.max_connecting = -1;
+    options.loglevel = -1;
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        options.loglevels[i] = -1;
+    }
+    
+    int i;
+    for (i = 1; i < argc; i++) {
+        char *arg = argv[i];
+        if (!strcmp(arg, "--help")) {
+            options.help = 1;
+        }
+        else if (!strcmp(arg, "--version")) {
+            options.version = 1;
+        }
+        else if (!strcmp(arg, "--connect-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.connect_addr = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--max-connections")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.max_connections = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--max-connecting")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.max_connecting = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--loglevel")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--channel-loglevel")) {
+            if (2 >= argc - i) {
+                fprintf(stderr, "%s: requires two arguments\n", arg);
+                return 0;
+            }
+            int channel = BLogGlobal_GetChannelByName(argv[i + 1]);
+            if (channel < 0) {
+                fprintf(stderr, "%s: wrong channel argument\n", arg);
+                return 0;
+            }
+            int loglevel = parse_loglevel(argv[i + 2]);
+            if (loglevel < 0) {
+                fprintf(stderr, "%s: wrong loglevel argument\n", arg);
+                return 0;
+            }
+            options.loglevels[channel] = loglevel;
+            i += 2;
+        }
+        else {
+            fprintf(stderr, "unknown option: %s\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    if (!options.connect_addr) {
+        fprintf(stderr, "--connect-addr missing\n");
+        return 0;
+    }
+    
+    if (options.max_connections == -1) {
+        fprintf(stderr, "--max-connections missing\n");
+        return 0;
+    }
+    
+    if (options.max_connecting == -1) {
+        fprintf(stderr, "--max-connecting missing\n");
+        return 0;
+    }
+    
+    return 1;
+}
+
+int process_arguments (void)
+{
+    // resolve listen address
+    if (!BAddr_Parse(&connect_addr, options.connect_addr, NULL, 0)) {
+        BLog(BLOG_ERROR, "connect addr: BAddr_Parse failed");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void signal_handler (void *unused)
+{
+    BLog(BLOG_NOTICE, "termination requested");
+    
+    // exit event loop
+    BReactor_Quit(&reactor, 1);
+}
+
+int connection_new (void)
+{
+    // allocate structure
+    struct connection *conn = (struct connection *)malloc(sizeof(*conn));
+    if (!conn) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // set not connected
+    conn->connected = 0;
+    
+    // init connector
+    if (!BConnector_Init(&conn->connector, connect_addr, &reactor, conn, (BConnector_handler)connection_connector_handler)) {
+        BLog(BLOG_ERROR, "BConnector_Init failed");
+        goto fail1;
+    }
+    
+    // add to connections list
+    LinkedList1_Append(&connections_list, &conn->connections_list_node);
+    num_connections++;
+    num_connecting++;
+    
+    return 1;
+    
+fail1:
+    free(conn);
+fail0:
+    return 0;
+}
+
+void connection_free (struct connection *conn)
+{
+    // remove from connections list
+    LinkedList1_Remove(&connections_list, &conn->connections_list_node);
+    num_connections--;
+    if (!conn->connected) {
+        num_connecting--;
+    }
+    
+    if (conn->connected) {
+        // free receive interface
+        BConnection_RecvAsync_Free(&conn->con);
+        
+        // free connection
+        BConnection_Free(&conn->con);
+    }
+    
+    // free connector
+    BConnector_Free(&conn->connector);
+    
+    // free structure
+    free(conn);
+}
+
+void connection_logfunc (struct connection *conn)
+{
+    BLog_Append("%d connection (%p): ", num_connecting, (void *)conn);
+}
+
+void connection_log (struct connection *conn, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_LogViaFuncVarArg((BLog_logfunc)connection_logfunc, conn, BLOG_CURRENT_CHANNEL, level, fmt, vl);
+    va_end(vl);
+}
+
+void connection_connector_handler (struct connection *conn, int is_error)
+{
+    ASSERT(!conn->connected)
+    
+    // check for connection error
+    if (is_error) {
+        connection_log(conn, BLOG_INFO, "failed to connect");
+        goto fail0;
+    }
+    
+    // init connection from connector
+    if (!BConnection_Init(&conn->con, BConnection_source_connector(&conn->connector), &reactor, conn, (BConnection_handler)connection_connection_handler)) {
+        connection_log(conn, BLOG_INFO, "BConnection_Init failed");
+        goto fail0;
+    }
+    
+    // init receive interface
+    BConnection_RecvAsync_Init(&conn->con);
+    conn->recv_if = BConnection_RecvAsync_GetIf(&conn->con);
+    StreamRecvInterface_Receiver_Init(conn->recv_if, (StreamRecvInterface_handler_done)connection_recv_handler_done, conn);
+    
+    // start receiving
+    StreamRecvInterface_Receiver_Recv(conn->recv_if, conn->buf, sizeof(conn->buf));
+    
+    // no longer connecting
+    conn->connected = 1;
+    num_connecting--;
+    
+    connection_log(conn, BLOG_INFO, "connected");
+    
+    // schedule making connections (because of connecting limit)
+    BReactor_SetTimer(&reactor, &make_connections_timer);
+    return;
+    
+fail0:
+    // free connection
+    connection_free(conn);
+    
+    // schedule making connections
+    BReactor_SetTimer(&reactor, &make_connections_timer);
+}
+
+void connection_connection_handler (struct connection *conn, int event)
+{
+    ASSERT(conn->connected)
+    
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        connection_log(conn, BLOG_INFO, "connection closed");
+    } else {
+        connection_log(conn, BLOG_INFO, "connection error");
+    }
+    
+    // free connection
+    connection_free(conn);
+    
+    // schedule making connections
+    BReactor_SetTimer(&reactor, &make_connections_timer);
+}
+
+void connection_recv_handler_done (struct connection *conn, int data_len)
+{
+    ASSERT(conn->connected)
+    
+    // receive more
+    StreamRecvInterface_Receiver_Recv(conn->recv_if, conn->buf, sizeof(conn->buf));
+    
+    connection_log(conn, BLOG_INFO, "received %d bytes", data_len);
+}
+
+void make_connections_timer_handler (void *unused)
+{
+    int make_num = bmin_int(options.max_connections - num_connections, options.max_connecting - num_connecting);
+    
+    if (make_num <= 0) {
+        return;
+    }
+    
+    BLog(BLOG_INFO, "making %d connections", make_num);
+    
+    for (int i = 0; i < make_num; i++) {
+        if (!connection_new()) {
+            // can happen if fd limit is reached
+            BLog(BLOG_ERROR, "failed to make connection, waiting");
+            BReactor_SetTimerAfter(&reactor, &make_connections_timer, 10);
+            return;
+        }
+    }
+}
diff --git a/external/badvpn_dns/dostest/dostest-server.c b/external/badvpn_dns/dostest/dostest-server.c
new file mode 100644
index 0000000..7447591
--- /dev/null
+++ b/external/badvpn_dns/dostest/dostest-server.c
@@ -0,0 +1,567 @@
+/**
+ * @file dostest-server.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#ifdef BADVPN_LINUX
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+
+#include <misc/debug.h>
+#include <misc/version.h>
+#include <misc/offset.h>
+#include <misc/open_standard_streams.h>
+#include <misc/balloc.h>
+#include <misc/loglevel.h>
+#include <structure/LinkedList1.h>
+#include <base/BLog.h>
+#include <system/BAddr.h>
+#include <system/BReactor.h>
+#include <system/BNetwork.h>
+#include <system/BConnection.h>
+#include <system/BSignal.h>
+#include "StreamBuffer.h"
+
+#include <generated/blog_channel_dostest_server.h>
+
+#define PROGRAM_NAME "dostest-server"
+
+#ifdef BADVPN_LINUX
+#ifndef SO_DOSDEF_PREPARE
+#define SO_DOSDEF_PREPARE 48
+#endif
+#ifndef SO_DOSDEF_ACTIVATE
+#define SO_DOSDEF_ACTIVATE 49
+#endif
+#endif
+
+#define BUF_SIZE 1024
+
+// client structure
+struct client {
+    BConnection con;
+    BAddr addr;
+    StreamBuffer buf;
+    BTimer disconnect_timer;
+    LinkedList1Node clients_list_node;
+};
+
+// command-line options
+static struct {
+    int help;
+    int version;
+    char *listen_addr;
+    int max_clients;
+    int disconnect_time;
+    int defense_prepare_clients;
+    int defense_activate_clients;
+    int loglevel;
+    int loglevels[BLOG_NUM_CHANNELS];
+} options;
+
+// listen address
+static BAddr listen_addr;
+
+// reactor
+static BReactor ss;
+
+// listener
+static BListener listener;
+
+// clients
+static LinkedList1 clients_list;
+static int num_clients;
+
+// defense status
+static int defense_prepare;
+static int defense_activate;
+
+static void print_help (const char *name);
+static void print_version (void);
+static int parse_arguments (int argc, char *argv[]);
+static int process_arguments (void);
+static void signal_handler (void *unused);
+static void listener_handler (void *unused);
+static void client_free (struct client *client);
+static void client_logfunc (struct client *client);
+static void client_log (struct client *client, int level, const char *fmt, ...);
+static void client_disconnect_timer_handler (struct client *client);
+static void client_connection_handler (struct client *client, int event);
+static void update_defense (void);
+
+int main (int argc, char **argv)
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    // open standard streams
+    open_standard_streams();
+    
+    // parse command-line arguments
+    if (!parse_arguments(argc, argv)) {
+        fprintf(stderr, "Failed to parse arguments\n");
+        print_help(argv[0]);
+        goto fail0;
+    }
+    
+    // handle --help and --version
+    if (options.help) {
+        print_version();
+        print_help(argv[0]);
+        return 0;
+    }
+    if (options.version) {
+        print_version();
+        return 0;
+    }
+    
+    // init loger
+    BLog_InitStderr();
+    
+    // configure logger channels
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        if (options.loglevels[i] >= 0) {
+            BLog_SetChannelLoglevel(i, options.loglevels[i]);
+        }
+        else if (options.loglevel >= 0) {
+            BLog_SetChannelLoglevel(i, options.loglevel);
+        }
+    }
+    
+    BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION);
+    
+    // initialize network
+    if (!BNetwork_GlobalInit()) {
+        BLog(BLOG_ERROR, "BNetwork_GlobalInit failed");
+        goto fail1;
+    }
+    
+    // process arguments
+    if (!process_arguments()) {
+        BLog(BLOG_ERROR, "Failed to process arguments");
+        goto fail1;
+    }
+    
+    // init time
+    BTime_Init();
+    
+    // init reactor
+    if (!BReactor_Init(&ss)) {
+        BLog(BLOG_ERROR, "BReactor_Init failed");
+        goto fail1;
+    }
+    
+    // setup signal handler
+    if (!BSignal_Init(&ss, signal_handler, NULL)) {
+        BLog(BLOG_ERROR, "BSignal_Init failed");
+        goto fail2;
+    }
+    
+    // initialize listener
+    if (!BListener_Init(&listener, listen_addr, &ss, NULL, listener_handler)) {
+        BLog(BLOG_ERROR, "Listener_Init failed");
+        goto fail3;
+    }
+    
+    // init clients list
+    LinkedList1_Init(&clients_list);
+    num_clients = 0;
+    
+    // clear defense state
+    defense_prepare = 0;
+    defense_activate = 0;
+    
+    // update defense
+    update_defense();
+    
+    // enter event loop
+    BLog(BLOG_NOTICE, "entering event loop");
+    BReactor_Exec(&ss);
+    
+    // free clients
+    while (!LinkedList1_IsEmpty(&clients_list)) {
+        struct client *client = UPPER_OBJECT(LinkedList1_GetFirst(&clients_list), struct client, clients_list_node);
+        client_free(client);
+    }
+    // free listener
+    BListener_Free(&listener);
+fail3:
+    // free signal
+    BSignal_Finish();
+fail2:
+    // free reactor
+    BReactor_Free(&ss);
+fail1:
+    // free logger
+    BLog(BLOG_NOTICE, "exiting");
+    BLog_Free();
+fail0:
+    // finish debug objects
+    DebugObjectGlobal_Finish();
+    
+    return 1;
+}
+
+void print_help (const char *name)
+{
+    printf(
+        "Usage:\n"
+        "    %s\n"
+        "        [--help]\n"
+        "        [--version]\n"
+        "        --listen-addr <addr>\n"
+        "        --max-clients <number>\n"
+        "        --disconnect-time <milliseconds>\n"
+        "        [--defense-prepare-clients <number>]\n"
+        "        [--defense-activate-clients <number>]\n"
+        "        [--loglevel <0-5/none/error/warning/notice/info/debug>]\n"
+        "        [--channel-loglevel <channel-name> <0-5/none/error/warning/notice/info/debug>] ...\n"
+        "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n",
+        name
+    );
+}
+
+void print_version (void)
+{
+    printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n");
+}
+
+int parse_arguments (int argc, char *argv[])
+{
+    options.help = 0;
+    options.version = 0;
+    options.listen_addr = NULL;
+    options.max_clients = -1;
+    options.disconnect_time = -1;
+    options.defense_prepare_clients = -1;
+    options.defense_activate_clients = -1;
+    options.loglevel = -1;
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        options.loglevels[i] = -1;
+    }
+    
+    int i;
+    for (i = 1; i < argc; i++) {
+        char *arg = argv[i];
+        if (!strcmp(arg, "--help")) {
+            options.help = 1;
+        }
+        else if (!strcmp(arg, "--version")) {
+            options.version = 1;
+        }
+        else if (!strcmp(arg, "--listen-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.listen_addr = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--max-clients")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.max_clients = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--disconnect-time")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.disconnect_time = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--defense-prepare-clients")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.defense_prepare_clients = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--defense-activate-clients")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.defense_activate_clients = atoi(argv[i + 1])) <= 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--loglevel")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--channel-loglevel")) {
+            if (2 >= argc - i) {
+                fprintf(stderr, "%s: requires two arguments\n", arg);
+                return 0;
+            }
+            int channel = BLogGlobal_GetChannelByName(argv[i + 1]);
+            if (channel < 0) {
+                fprintf(stderr, "%s: wrong channel argument\n", arg);
+                return 0;
+            }
+            int loglevel = parse_loglevel(argv[i + 2]);
+            if (loglevel < 0) {
+                fprintf(stderr, "%s: wrong loglevel argument\n", arg);
+                return 0;
+            }
+            options.loglevels[channel] = loglevel;
+            i += 2;
+        }
+        else {
+            fprintf(stderr, "unknown option: %s\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    if (!options.listen_addr) {
+        fprintf(stderr, "--listen-addr missing\n");
+        return 0;
+    }
+    
+    if (options.max_clients == -1) {
+        fprintf(stderr, "--max-clients missing\n");
+        return 0;
+    }
+    
+    if (options.disconnect_time == -1) {
+        fprintf(stderr, "--disconnect-time missing\n");
+        return 0;
+    }
+    
+    return 1;
+}
+
+int process_arguments (void)
+{
+    // resolve listen address
+    if (!BAddr_Parse(&listen_addr, options.listen_addr, NULL, 0)) {
+        BLog(BLOG_ERROR, "listen addr: BAddr_Parse failed");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void signal_handler (void *unused)
+{
+    BLog(BLOG_NOTICE, "termination requested");
+    
+    // exit event loop
+    BReactor_Quit(&ss, 1);
+}
+
+void listener_handler (void *unused)
+{
+    if (num_clients == options.max_clients) {
+        BLog(BLOG_ERROR, "maximum number of clients reached");
+        goto fail0;
+    }
+    
+    // allocate structure
+    struct client *client = (struct client *)malloc(sizeof(*client));
+    if (!client) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // accept client
+    if (!BConnection_Init(&client->con, BConnection_source_listener(&listener, &client->addr), &ss, client, (BConnection_handler)client_connection_handler)) {
+        BLog(BLOG_ERROR, "BConnection_Init failed");
+        goto fail1;
+    }
+    
+    // init connection interfaces
+    BConnection_RecvAsync_Init(&client->con);
+    BConnection_SendAsync_Init(&client->con);
+    StreamRecvInterface *recv_if = BConnection_RecvAsync_GetIf(&client->con);
+    StreamPassInterface *send_if = BConnection_SendAsync_GetIf(&client->con);
+    
+    // init stream buffer (to loop received data back to the client)
+    if (!StreamBuffer_Init(&client->buf, BUF_SIZE, recv_if, send_if)) {
+        BLog(BLOG_ERROR, "StreamBuffer_Init failed");
+        goto fail2;
+    }
+    
+    // init disconnect timer
+    BTimer_Init(&client->disconnect_timer, options.disconnect_time, (BTimer_handler)client_disconnect_timer_handler, client);
+    BReactor_SetTimer(&ss, &client->disconnect_timer);
+    
+    // insert to clients list
+    LinkedList1_Append(&clients_list, &client->clients_list_node);
+    num_clients++;
+    
+    client_log(client, BLOG_INFO, "connected");
+    BLog(BLOG_NOTICE, "%d clients", num_clients);
+    
+    // update defense
+    update_defense();
+    return;
+    
+fail2:
+    BConnection_SendAsync_Free(&client->con);
+    BConnection_RecvAsync_Free(&client->con);
+    BConnection_Free(&client->con);
+fail1:
+    free(client);
+fail0:
+    return;
+}
+
+void client_free (struct client *client)
+{
+    // remove from clients list
+    LinkedList1_Remove(&clients_list, &client->clients_list_node);
+    num_clients--;
+    
+    // free disconnect timer
+    BReactor_RemoveTimer(&ss, &client->disconnect_timer);
+    
+    // free stream buffer
+    StreamBuffer_Free(&client->buf);
+    
+    // free connection interfaces
+    BConnection_SendAsync_Free(&client->con);
+    BConnection_RecvAsync_Free(&client->con);
+    
+    // free connection
+    BConnection_Free(&client->con);
+    
+    // free structure
+    free(client);
+    
+    BLog(BLOG_NOTICE, "%d clients", num_clients);
+    
+    // update defense
+    update_defense();
+}
+
+void client_logfunc (struct client *client)
+{
+    char addr[BADDR_MAX_PRINT_LEN];
+    BAddr_Print(&client->addr, addr);
+    
+    BLog_Append("client (%s): ", addr);
+}
+
+void client_log (struct client *client, int level, const char *fmt, ...)
+{
+    va_list vl;
+    va_start(vl, fmt);
+    BLog_LogViaFuncVarArg((BLog_logfunc)client_logfunc, client, BLOG_CURRENT_CHANNEL, level, fmt, vl);
+    va_end(vl);
+}
+
+void client_disconnect_timer_handler (struct client *client)
+{
+    client_log(client, BLOG_INFO, "timed out, disconnecting");
+    
+    // free client
+    client_free(client);
+}
+
+void client_connection_handler (struct client *client, int event)
+{
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        client_log(client, BLOG_INFO, "client closed");
+    } else {
+        client_log(client, BLOG_INFO, "client error");
+    }
+    
+    // free client
+    client_free(client);
+}
+
+void update_defense (void)
+{
+#ifdef BADVPN_LINUX
+    if (options.defense_prepare_clients != -1) {
+        int val = num_clients >= options.defense_prepare_clients;
+        int res = setsockopt(listener.fd, SOL_SOCKET, SO_DOSDEF_PREPARE, &val, sizeof(val));
+        if (res < 0) {
+            BLog(BLOG_ERROR, "failed to %s defense preparation", (val ? "enable" : "disable"));
+        } else {
+            if (!defense_prepare && val) {
+                BLog(BLOG_NOTICE, "defense preparation enabled");
+            }
+            else if (defense_prepare && !val) {
+                BLog(BLOG_NOTICE, "defense preparation disabled");
+            }
+        }
+        defense_prepare = val;
+    }
+    
+    if (options.defense_activate_clients != -1) {
+        int val = num_clients >= options.defense_activate_clients;
+        int res = setsockopt(listener.fd, SOL_SOCKET, SO_DOSDEF_ACTIVATE, &val, sizeof(val));
+        if (res < 0) {
+            BLog(BLOG_ERROR, "failed to %s defense activation", (val ? "enable" : "disable"));
+        } else {
+            if (!defense_activate && val) {
+                BLog(BLOG_NOTICE, "defense activation enabled");
+            }
+            else if (defense_activate && !val) {
+                BLog(BLOG_NOTICE, "defense activation disabled");
+            }
+        }
+        defense_activate = val;
+    }
+#endif
+}
diff --git a/external/badvpn_dns/examples/CMakeLists.txt b/external/badvpn_dns/examples/CMakeLists.txt
new file mode 100644
index 0000000..27dbeaa
--- /dev/null
+++ b/external/badvpn_dns/examples/CMakeLists.txt
@@ -0,0 +1,97 @@
+if (NOT EMSCRIPTEN)
+    add_executable(btimer_example btimer_example.c)
+    target_link_libraries(btimer_example system)
+endif ()
+
+if (BUILDING_PREDICATE)
+    add_executable(predicate_test predicate_test.c)
+    target_link_libraries(predicate_test predicate)
+endif ()
+
+if (NOT EMSCRIPTEN)
+    add_executable(fairqueue_test fairqueue_test.c)
+    target_link_libraries(fairqueue_test system flow)
+endif ()
+
+add_executable(indexedlist_test indexedlist_test.c)
+
+if (BUILDING_SECURITY)
+    add_executable(fairqueue_test2 fairqueue_test2.c)
+    target_link_libraries(fairqueue_test2 system flow security)
+
+    add_executable(bavl_test bavl_test.c)
+    target_link_libraries(bavl_test security)
+
+    add_executable(savl_test savl_test.c)
+    target_link_libraries(savl_test security)
+
+    add_executable(bencryption_bench bencryption_bench.c)
+    target_link_libraries(bencryption_bench system security)
+endif ()
+
+if (BUILD_NCD)
+    add_executable(ncd_tokenizer_test ncd_tokenizer_test.c)
+    target_link_libraries(ncd_tokenizer_test ncdtokenizer)
+
+    add_executable(ncd_parser_test ncd_parser_test.c)
+    target_link_libraries(ncd_parser_test ncdconfigparser ncdvalgenerator ncdsugar)
+
+    add_executable(ncd_value_parser_test ncd_value_parser_test.c)
+    target_link_libraries(ncd_value_parser_test ncdvalparser ncdvalgenerator)
+
+    if (NOT EMSCRIPTEN)
+        add_executable(ncdinterfacemonitor_test ncdinterfacemonitor_test.c)
+        target_link_libraries(ncdinterfacemonitor_test ncdinterfacemonitor)
+    endif ()
+
+    add_executable(ncdval_test ncdval_test.c)
+    target_link_libraries(ncdval_test ncdval)
+    
+    add_executable(ncdvalcons_test ncdvalcons_test.c)
+    target_link_libraries(ncdvalcons_test ncdvalcons ncdvalgenerator)
+endif ()
+
+if (BUILDING_UDEVMONITOR)
+    add_executable(ncdudevmonitor_test ncdudevmonitor_test.c)
+    target_link_libraries(ncdudevmonitor_test udevmonitor)
+
+    add_executable(ncdudevmanager_test ncdudevmanager_test.c)
+    target_link_libraries(ncdudevmanager_test udevmonitor)
+endif ()
+
+if (NOT WIN32 AND NOT EMSCRIPTEN)
+    add_executable(bprocess_example bprocess_example.c)
+    target_link_libraries(bprocess_example system)
+
+    add_executable(stdin_input stdin_input.c)
+    target_link_libraries(stdin_input system flow flowextra)
+endif ()
+
+if (BUILDING_DHCPCLIENT)
+    add_executable(dhcpclient_test dhcpclient_test.c)
+    target_link_libraries(dhcpclient_test dhcpclient)
+endif ()
+
+if (BUILDING_ARPPROBE)
+    add_executable(arpprobe_test arpprobe_test.c)
+    target_link_libraries(arpprobe_test arpprobe)
+endif ()
+
+add_executable(substring_test substring_test.c)
+
+if (NOT WIN32)
+    add_executable(ipaddr6_test ipaddr6_test.c)
+    add_executable(parse_number_test parse_number_test.c)
+endif ()
+
+if (BUILDING_RANDOM)
+    add_executable(brandom2_test brandom2_test.c)
+    target_link_libraries(brandom2_test badvpn_random)
+endif ()
+
+add_executable(cavl_test cavl_test.c)
+
+if (EMSCRIPTEN)
+    add_executable(emscripten_test emscripten_test.c)
+    target_link_libraries(emscripten_test system)
+endif ()
diff --git a/external/badvpn_dns/examples/FastPacketSource.h b/external/badvpn_dns/examples/FastPacketSource.h
new file mode 100644
index 0000000..e13e2f2
--- /dev/null
+++ b/external/badvpn_dns/examples/FastPacketSource.h
@@ -0,0 +1,79 @@
+/**
+ * @file FastPacketSource.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FASTPACKETSOURCE_H
+#define _FASTPACKETSOURCE_H
+
+#include <stdint.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+typedef struct {
+    PacketPassInterface *output;
+    int psize;
+    uint8_t *data;
+    int data_len;
+    DebugObject d_obj;
+} FastPacketSource;
+
+static void _FastPacketSource_output_handler_done (FastPacketSource *s)
+{
+    DebugObject_Access(&s->d_obj);
+    
+    PacketPassInterface_Sender_Send(s->output, s->data, s->data_len);
+}
+
+static void FastPacketSource_Init (FastPacketSource *s, PacketPassInterface *output, uint8_t *data, int data_len, BPendingGroup *pg)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= PacketPassInterface_GetMTU(output));
+    
+    // init arguments
+    s->output = output;
+    s->data = data;
+    s->data_len = data_len;
+    
+    // init output
+    PacketPassInterface_Sender_Init(s->output, (PacketPassInterface_handler_done)_FastPacketSource_output_handler_done, s);
+    
+    // schedule send
+    PacketPassInterface_Sender_Send(s->output, s->data, s->data_len);
+    
+    DebugObject_Init(&s->d_obj);
+}
+
+static void FastPacketSource_Free (FastPacketSource *s)
+{
+    DebugObject_Free(&s->d_obj);
+}
+
+#endif
diff --git a/external/badvpn_dns/examples/RandomPacketSink.h b/external/badvpn_dns/examples/RandomPacketSink.h
new file mode 100644
index 0000000..cbadf78
--- /dev/null
+++ b/external/badvpn_dns/examples/RandomPacketSink.h
@@ -0,0 +1,116 @@
+/**
+ * @file RandomPacketSink.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RANDOMPACKETSINK_H
+#define _RANDOMPACKETSINK_H
+
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <security/BRandom.h>
+#include <system/BReactor.h>
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+typedef struct {
+    BReactor *reactor;
+    PacketPassInterface input;
+    BTimer timer;
+    DebugObject d_obj;
+} RandomPacketSink;
+
+static void _RandomPacketSink_input_handler_send (RandomPacketSink *s, uint8_t *data, int data_len)
+{
+    DebugObject_Access(&s->d_obj);
+    
+    printf("sink: send '");
+    size_t res = fwrite(data, data_len, 1, stdout);
+    B_USE(res)
+    
+    uint8_t r;
+    BRandom_randomize(&r, sizeof(r));
+    if (r&(uint8_t)1) {
+        printf("' accepting\n");
+        PacketPassInterface_Done(&s->input);
+    } else {
+        printf("' delaying\n");
+        BReactor_SetTimer(s->reactor, &s->timer);
+    }
+}
+
+static void _RandomPacketSink_input_handler_requestcancel (RandomPacketSink *s)
+{
+    DebugObject_Access(&s->d_obj);
+    
+    printf("sink: cancelled\n");
+    BReactor_RemoveTimer(s->reactor, &s->timer);
+    PacketPassInterface_Done(&s->input);
+}
+
+static void _RandomPacketSink_timer_handler (RandomPacketSink *s)
+{
+    DebugObject_Access(&s->d_obj);
+    
+    PacketPassInterface_Done(&s->input);
+}
+
+static void RandomPacketSink_Init (RandomPacketSink *s, BReactor *reactor, int mtu, int ms)
+{
+    // init arguments
+    s->reactor = reactor;
+    
+    // init input
+    PacketPassInterface_Init(&s->input, mtu, (PacketPassInterface_handler_send)_RandomPacketSink_input_handler_send, s, BReactor_PendingGroup(reactor));
+    PacketPassInterface_EnableCancel(&s->input, (PacketPassInterface_handler_requestcancel)_RandomPacketSink_input_handler_requestcancel);
+    
+    // init timer
+    BTimer_Init(&s->timer, ms, (BTimer_handler)_RandomPacketSink_timer_handler, s);
+    
+    DebugObject_Init(&s->d_obj);
+}
+
+static void RandomPacketSink_Free (RandomPacketSink *s)
+{
+    DebugObject_Free(&s->d_obj);
+    
+    // free timer
+    BReactor_RemoveTimer(s->reactor, &s->timer);
+    
+    // free input
+    PacketPassInterface_Free(&s->input);
+}
+
+static PacketPassInterface * RandomPacketSink_GetInput (RandomPacketSink *s)
+{
+    DebugObject_Access(&s->d_obj);
+    
+    return &s->input;
+}
+
+#endif
diff --git a/external/badvpn_dns/examples/TimerPacketSink.h b/external/badvpn_dns/examples/TimerPacketSink.h
new file mode 100644
index 0000000..e1e8217
--- /dev/null
+++ b/external/badvpn_dns/examples/TimerPacketSink.h
@@ -0,0 +1,97 @@
+/**
+ * @file TimerPacketSink.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TIMERPACKETSINK_H
+#define _TIMERPACKETSINK_H
+
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <system/BReactor.h>
+#include <flow/PacketPassInterface.h>
+
+typedef struct {
+    BReactor *reactor;
+    PacketPassInterface input;
+    BTimer timer;
+} TimerPacketSink;
+
+static void _TimerPacketSink_input_handler_send (TimerPacketSink *s, uint8_t *data, int data_len)
+{
+    printf("sink: send '");
+    size_t res = fwrite(data, data_len, 1, stdout);
+    B_USE(res)
+    printf("'\n");
+    
+    BReactor_SetTimer(s->reactor, &s->timer);
+}
+
+static void _TimerPacketSink_input_handler_requestcancel (TimerPacketSink *s)
+{
+    printf("sink: cancelled\n");
+    
+    BReactor_RemoveTimer(s->reactor, &s->timer);
+    PacketPassInterface_Done(&s->input);
+}
+
+static void _TimerPacketSink_timer_handler (TimerPacketSink *s)
+{
+    printf("sink: done\n");
+    
+    PacketPassInterface_Done(&s->input);
+}
+
+static void TimerPacketSink_Init (TimerPacketSink *s, BReactor *reactor, int mtu, int ms)
+{
+    // init arguments
+    s->reactor = reactor;
+    
+    // init input
+    PacketPassInterface_Init(&s->input, mtu, (PacketPassInterface_handler_send)_TimerPacketSink_input_handler_send, s, BReactor_PendingGroup(s->reactor));
+    PacketPassInterface_EnableCancel(&s->input, (PacketPassInterface_handler_requestcancel)_TimerPacketSink_input_handler_requestcancel);
+    
+    // init timer
+    BTimer_Init(&s->timer, ms, (BTimer_handler)_TimerPacketSink_timer_handler, s);
+}
+
+static void TimerPacketSink_Free (TimerPacketSink *s)
+{
+    // free timer
+    BReactor_RemoveTimer(s->reactor, &s->timer);
+    
+    // free input
+    PacketPassInterface_Free(&s->input);
+}
+
+static PacketPassInterface * TimerPacketSink_GetInput (TimerPacketSink *s)
+{
+    return &s->input;
+}
+
+#endif
diff --git a/external/badvpn_dns/examples/arpprobe_test.c b/external/badvpn_dns/examples/arpprobe_test.c
new file mode 100644
index 0000000..d075f52
--- /dev/null
+++ b/external/badvpn_dns/examples/arpprobe_test.c
@@ -0,0 +1,131 @@
+/**
+ * @file arpprobe_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <system/BTime.h>
+#include <system/BNetwork.h>
+#include <arpprobe/BArpProbe.h>
+
+BReactor reactor;
+BArpProbe arpprobe;
+
+static void signal_handler (void *user);
+static void arpprobe_handler (void *unused, int event);
+
+int main (int argc, char **argv)
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    if (argc != 3) {
+        printf("Usage: %s <interface> <addr>\n", argv[0]);
+        goto fail0;
+    }
+    
+    char *ifname = argv[1];
+    uint32_t addr = inet_addr(argv[2]);
+    
+    BTime_Init();
+    
+    BLog_InitStdout();
+    
+    if (!BNetwork_GlobalInit()) {
+        DEBUG("BNetwork_GlobalInit failed");
+        goto fail1;
+    }
+    
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        goto fail1;
+    }
+    
+    if (!BSignal_Init(&reactor, signal_handler, NULL)) {
+        DEBUG("BSignal_Init failed");
+        goto fail2;
+    }
+    
+    if (!BArpProbe_Init(&arpprobe, ifname, addr, &reactor, NULL, arpprobe_handler)) {
+        DEBUG("BArpProbe_Init failed");
+        goto fail3;
+    }
+    
+    BReactor_Exec(&reactor);
+    
+    BArpProbe_Free(&arpprobe);
+fail3:
+    BSignal_Finish();
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return 1;
+}
+
+void signal_handler (void *user)
+{
+    DEBUG("termination requested");
+    
+    BReactor_Quit(&reactor, 0);
+}
+
+void arpprobe_handler (void *unused, int event)
+{
+    switch (event) {
+        case BARPPROBE_EVENT_EXIST: {
+            printf("ARPPROBE: exist\n");
+        } break;
+        
+        case BARPPROBE_EVENT_NOEXIST: {
+            printf("ARPPROBE: noexist\n");
+        } break;
+        
+        case BARPPROBE_EVENT_ERROR: {
+            printf("ARPPROBE: error\n");
+            
+            // exit reactor
+            BReactor_Quit(&reactor, 0);
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
diff --git a/external/badvpn_dns/examples/bavl_test.c b/external/badvpn_dns/examples/bavl_test.c
new file mode 100644
index 0000000..c30ae6f
--- /dev/null
+++ b/external/badvpn_dns/examples/bavl_test.c
@@ -0,0 +1,129 @@
+/**
+ * @file bavl_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include <misc/offset.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <structure/BAVL.h>
+#include <security/BRandom.h>
+
+struct mynode {
+    int used;
+    int num;
+    BAVLNode avl_node;
+};
+
+static int int_comparator (void *user, int *val1, int *val2)
+{
+    return B_COMPARE(*val1, *val2);
+}
+
+static void verify (BAVL *tree)
+{
+    printf("Verifying...\n");
+    BAVL_Verify(tree);
+}
+
+int main (int argc, char **argv)
+{
+    int num_nodes;
+    int num_random_delete;
+    
+    if (argc != 3 || (num_nodes = atoi(argv[1])) <= 0 || (num_random_delete = atoi(argv[2])) < 0) {
+        fprintf(stderr, "Usage: %s <num> <numrandomdelete>\n", (argc > 0 ? argv[0] : NULL));
+        return 1;
+    }
+    
+    struct mynode *nodes = (struct mynode *)BAllocArray(num_nodes, sizeof(*nodes));
+    ASSERT_FORCE(nodes)
+    
+    int *values_ins = (int *)BAllocArray(num_nodes, sizeof(int));
+    ASSERT_FORCE(values_ins)
+    
+    int *values = (int *)BAllocArray(num_random_delete, sizeof(int));
+    ASSERT_FORCE(values)
+    
+    BAVL avl;
+    BAVL_Init(&avl, OFFSET_DIFF(struct mynode, num, avl_node), (BAVL_comparator)int_comparator, NULL);
+    verify(&avl);
+    
+    printf("Inserting random values...\n");
+    int inserted = 0;
+    BRandom_randomize((uint8_t *)values_ins, num_nodes * sizeof(int));
+    for (int i = 0; i < num_nodes; i++) {
+        nodes[i].num = values_ins[i];
+        if (BAVL_Insert(&avl, &nodes[i].avl_node, NULL)) {
+            nodes[i].used = 1;
+            inserted++;
+        } else {
+            nodes[i].used = 0;
+            printf("Insert collision!\n");
+        }
+    }
+    printf("Inserted %d entries\n", inserted);
+    verify(&avl);
+    
+    printf("Removing random entries...\n");
+    int removed1 = 0;
+    BRandom_randomize((uint8_t *)values, num_random_delete * sizeof(int));
+    for (int i = 0; i < num_random_delete; i++) {
+        int index = (((unsigned int *)values)[i] % num_nodes);
+        struct mynode *node = nodes + index;
+        if (node->used) {
+            BAVL_Remove(&avl, &node->avl_node);
+            node->used = 0;
+            removed1++;
+        }
+    }
+    printf("Removed %d entries\n", removed1);
+    verify(&avl);
+    
+    printf("Removing remaining...\n");
+    int removed2 = 0;
+    while (!BAVL_IsEmpty(&avl)) {
+        struct mynode *node = UPPER_OBJECT(BAVL_GetFirst(&avl), struct mynode, avl_node);
+        ASSERT_FORCE(node->used)
+        BAVL_Remove(&avl, &node->avl_node);
+        node->used = 0;
+        removed2++;
+    }
+    printf("Removed %d entries\n", removed2);
+    ASSERT_FORCE(BAVL_IsEmpty(&avl))
+    ASSERT_FORCE(removed1 + removed2 == inserted)
+    verify(&avl);
+    
+    BFree(nodes);
+    BFree(values_ins);
+    BFree(values);
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/bencryption_bench.c b/external/badvpn_dns/examples/bencryption_bench.c
new file mode 100644
index 0000000..c842bf2
--- /dev/null
+++ b/external/badvpn_dns/examples/bencryption_bench.c
@@ -0,0 +1,146 @@
+/**
+ * @file bencryption_bench.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <limits.h>
+
+#include <misc/balloc.h>
+#include <security/BRandom.h>
+#include <security/BEncryption.h>
+#include <base/DebugObject.h>
+
+static void usage (char *name)
+{
+    printf(
+        "Usage: %s <enc/dec> <ciper> <num_blocks> <num_ops>\n"
+        "    <cipher> is one of (blowfish, aes).\n",
+        name
+    );
+    
+    exit(1);
+}
+
+int main (int argc, char **argv)
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    if (argc != 5) {
+        usage(argv[0]);
+    }
+    
+    char *mode_str = argv[1];
+    char *cipher_str = argv[2];
+    
+    int mode;
+    int cipher = 0; // silence warning
+    int num_blocks = atoi(argv[3]);
+    int num_ops = atoi(argv[4]);
+    
+    if (!strcmp(mode_str, "enc")) {
+        mode = BENCRYPTION_MODE_ENCRYPT;
+    }
+    else if (!strcmp(mode_str, "dec")) {
+        mode = BENCRYPTION_MODE_DECRYPT;
+    }
+    else {
+        usage(argv[0]);
+    }
+    
+    if (!strcmp(cipher_str, "blowfish")) {
+        cipher = BENCRYPTION_CIPHER_BLOWFISH;
+    }
+    else if (!strcmp(cipher_str, "aes")) {
+        cipher = BENCRYPTION_CIPHER_AES;
+    }
+    else {
+        usage(argv[0]);
+    }
+    
+    if (num_blocks < 0 || num_ops < 0) {
+        usage(argv[0]);
+    }
+    
+    int key_size = BEncryption_cipher_key_size(cipher);
+    int block_size = BEncryption_cipher_block_size(cipher);
+    
+    uint8_t key[BENCRYPTION_MAX_KEY_SIZE];
+    BRandom_randomize(key, key_size);
+    
+    uint8_t iv[BENCRYPTION_MAX_BLOCK_SIZE];
+    BRandom_randomize(iv, block_size);
+    
+    if (num_blocks > INT_MAX / block_size) {
+        printf("too much");
+        goto fail0;
+    }
+    int unit_size = num_blocks * block_size;
+    
+    printf("unit size %d\n", unit_size);
+    
+    uint8_t *buf1 = (uint8_t *)BAlloc(unit_size);
+    if (!buf1) {
+        printf("BAlloc failed");
+        goto fail0;
+    }
+    
+    uint8_t *buf2 = (uint8_t *)BAlloc(unit_size);
+    if (!buf2) {
+        printf("BAlloc failed");
+        goto fail1;
+    }
+    
+    BEncryption enc;
+    BEncryption_Init(&enc, mode, cipher, key);
+    
+    uint8_t *in = buf1;
+    uint8_t *out = buf2;
+    BRandom_randomize(in, unit_size);
+    
+    for (int i = 0; i < num_ops; i++) {
+        BEncryption_Encrypt(&enc, in, out, unit_size, iv);
+        
+        uint8_t *t = in;
+        in = out;
+        out = t;
+    }
+    
+    BEncryption_Free(&enc);
+    BFree(buf2);
+fail1:
+    BFree(buf1);
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/bprocess_example.c b/external/badvpn_dns/examples/bprocess_example.c
new file mode 100644
index 0000000..0ece996
--- /dev/null
+++ b/external/badvpn_dns/examples/bprocess_example.c
@@ -0,0 +1,140 @@
+/**
+ * @file bprocess_example.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <unistd.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BUnixSignal.h>
+#include <system/BTime.h>
+#include <system/BProcess.h>
+
+BReactor reactor;
+BUnixSignal unixsignal;
+BProcessManager manager;
+BProcess process;
+
+static void unixsignal_handler (void *user, int signo);
+static void process_handler (void *user, int normally, uint8_t normally_exit_status);
+
+int main (int argc, char **argv)
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    int ret = 1;
+    
+    if (argc < 2) {
+        printf("Usage: %s <program> [argument ...]\n", argv[0]);
+        goto fail0;
+    }
+    
+    char *program = argv[1];
+    
+    // init time
+    BTime_Init();
+    
+    // init logger
+    BLog_InitStdout();
+    
+    // init reactor (event loop)
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        goto fail1;
+    }
+    
+    // choose signals to catch
+    sigset_t set;
+    sigemptyset(&set);
+    sigaddset(&set, SIGINT);
+    sigaddset(&set, SIGTERM);
+    
+    // init BUnixSignal for catching signals
+    if (!BUnixSignal_Init(&unixsignal, &reactor, set, unixsignal_handler, NULL)) {
+        DEBUG("BUnixSignal_Init failed");
+        goto fail2;
+    }
+    
+    // init process manager
+    if (!BProcessManager_Init(&manager, &reactor)) {
+        DEBUG("BProcessManager_Init failed");
+        goto fail3;
+    }
+    
+    char **p_argv = argv + 1;
+    
+    // map fds 0, 1, 2 in child to fds 0, 1, 2 in parent
+    int fds[] = { 0, 1, 2, -1 };
+    int fds_map[] = { 0, 1, 2 };
+    
+    // start child process
+    if (!BProcess_InitWithFds(&process, &manager, process_handler, NULL, program, p_argv, NULL, fds, fds_map)) {
+        DEBUG("BProcess_Init failed");
+        goto fail4;
+    }
+    
+    // enter event loop
+    ret = BReactor_Exec(&reactor);
+    
+    BProcess_Free(&process);
+fail4:
+    BProcessManager_Free(&manager);
+fail3:
+    BUnixSignal_Free(&unixsignal, 0);
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return ret;
+}
+
+void unixsignal_handler (void *user, int signo)
+{
+    DEBUG("received %s, terminating child", (signo == SIGINT ? "SIGINT" : "SIGTERM"));
+    
+    // send SIGTERM to child
+    BProcess_Terminate(&process);
+}
+
+void process_handler (void *user, int normally, uint8_t normally_exit_status)
+{
+    DEBUG("process terminated");
+    
+    int ret = (normally ? normally_exit_status : 1);
+    
+    // return from event loop
+    BReactor_Quit(&reactor, ret);
+}
diff --git a/external/badvpn_dns/examples/brandom2_test.c b/external/badvpn_dns/examples/brandom2_test.c
new file mode 100644
index 0000000..539735c
--- /dev/null
+++ b/external/badvpn_dns/examples/brandom2_test.c
@@ -0,0 +1,65 @@
+/**
+ * @file brandom2_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include <misc/debug.h>
+#include <random/BRandom2.h>
+
+#define NUM_NUMBERS 10
+
+static BRandom2 brandom;
+
+int main (int argc, char *argv[])
+{
+    int ret = 1;
+    
+    if (!BRandom2_Init(&brandom, 0)) {
+        DEBUG("BRandom2_Init failed");
+        goto fail0;
+    }
+    
+    uint32_t numbers[NUM_NUMBERS];
+    if (!BRandom2_GenBytes(&brandom, numbers, sizeof(numbers))) {
+        DEBUG("BRandom2_GenBytes failed");
+        goto fail1;
+    }
+    
+    for (int i = 0; i < NUM_NUMBERS; i++) {
+        printf("%"PRIu32"\n", numbers[i]);
+    }
+    
+    ret = 0;
+    
+fail1:
+    BRandom2_Free(&brandom);
+fail0:
+    return ret;
+}
diff --git a/external/badvpn_dns/examples/btimer_example.c b/external/badvpn_dns/examples/btimer_example.c
new file mode 100644
index 0000000..c4d8d54
--- /dev/null
+++ b/external/badvpn_dns/examples/btimer_example.c
@@ -0,0 +1,84 @@
+/**
+ * @file btimer_example.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <system/BReactor.h>
+#include <base/BLog.h>
+#include <system/BTime.h>
+
+// gives average firing rate 100kHz
+#define TIMER_NUM 500
+#define TIMER_MODULO 10
+
+BReactor sys;
+
+void handle_timer (BTimer *bt)
+{
+    #ifdef BADVPN_USE_WINAPI
+    btime_t time = btime_gettime() + rand()%TIMER_MODULO;
+    #else
+    btime_t time = btime_gettime() + random()%TIMER_MODULO;
+    #endif
+    BReactor_SetTimerAbsolute(&sys, bt, time);
+}
+
+int main ()
+{
+    BLog_InitStdout();
+    
+    #ifdef BADVPN_USE_WINAPI
+    srand(time(NULL));
+    #else
+    srandom(time(NULL));
+    #endif
+    
+    // init time
+    BTime_Init();
+
+    if (!BReactor_Init(&sys)) {
+        DEBUG("BReactor_Init failed");
+        return 1;
+    }
+    
+    BTimer timers[TIMER_NUM];
+
+    int i;
+    for (i=0; i<TIMER_NUM; i++) {
+        BTimer *timer = &timers[i];
+        BTimer_Init(timer, 0, (BTimer_handler)handle_timer, timer);
+        BReactor_SetTimer(&sys, timer);
+    }
+    
+    int ret = BReactor_Exec(&sys);
+    BReactor_Free(&sys);
+    return ret;
+}
diff --git a/external/badvpn_dns/examples/cavl_test.c b/external/badvpn_dns/examples/cavl_test.c
new file mode 100644
index 0000000..61fcbd6
--- /dev/null
+++ b/external/badvpn_dns/examples/cavl_test.c
@@ -0,0 +1,285 @@
+/**
+ * @file cavl_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdint.h>
+#include <limits.h>
+
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <misc/debug.h>
+#include <misc/print_macros.h>
+#include <structure/CAvl.h>
+
+#define USE_COUNTS 0
+#define USE_ASSOC 1
+
+typedef size_t entry_index;
+#define MAX_INDICES SIZE_MAX
+
+typedef uint32_t entry_key;
+
+typedef uint8_t assoc_value;
+typedef uint64_t assoc_sum;
+
+struct entry {
+    entry_index tree_child[2];
+    entry_index tree_parent;
+    int8_t tree_balance;
+#if USE_COUNTS
+    size_t tree_count;
+#endif
+#if USE_ASSOC
+    assoc_value assoc_value;
+    assoc_sum assoc_sum;
+#endif
+    entry_key key;
+};
+
+typedef struct entry *entry_ptr;
+
+#include "cavl_test_tree.h"
+#include <structure/CAvl_decl.h>
+
+#include "cavl_test_tree.h"
+#include <structure/CAvl_impl.h>
+
+static void random_bytes (char *buf, size_t len)
+{
+    while (len > 0) {
+        *((unsigned char *)buf) = rand();
+        buf++;
+        len--;
+    }
+}
+
+static int uint64_less (void *user, uint64_t a, uint64_t b)
+{
+    return (a < b);
+}
+
+#if USE_ASSOC
+static MyTreeRef assoc_continue_last_lesser_equal (MyTree *tree, struct entry *arg, MyTreeRef ref, assoc_sum target_sum)
+{
+    assoc_sum cur_sum = MyTree_ExclusiveAssocPrefixSum(tree, arg, ref);
+    ASSERT(target_sum >= cur_sum)
+    while (cur_sum + ref.ptr->assoc_value <= target_sum) {
+        MyTreeRef next_ref = MyTree_GetNext(tree, arg, ref);
+        if (next_ref.link == -1) {
+            break;
+        }
+        cur_sum += ref.ptr->assoc_value;
+        ref = next_ref;
+    }
+    return ref;
+}
+#endif
+
+static void test_assoc (MyTree *tree, struct entry *arg)
+{
+#if USE_ASSOC
+    assoc_sum sum = 0;
+    for (MyTreeRef ref = MyTree_GetFirst(tree, arg); ref.link != -1; ref = MyTree_GetNext(tree, arg, ref)) {
+        assoc_sum tree_sum = MyTree_ExclusiveAssocPrefixSum(tree, arg, ref);
+        ASSERT_FORCE(tree_sum == sum);
+        ASSERT_FORCE(MyTree_FindLastExclusiveAssocPrefixSumLesserEqual(tree, arg, sum, uint64_less, NULL).link == assoc_continue_last_lesser_equal(tree, arg, ref, sum).link);
+        ASSERT_FORCE(MyTree_FindLastExclusiveAssocPrefixSumLesserEqual(tree, arg, sum + 1, uint64_less, NULL).link == assoc_continue_last_lesser_equal(tree, arg, ref, sum + 1).link);
+        sum += ref.ptr->assoc_value;
+    }
+    ASSERT_FORCE(sum == MyTree_AssocSum(tree, arg));
+#endif
+}
+
+int main (int argc, char *argv[])
+{
+    //srand(time(NULL));
+    
+    printf("sizeof(struct entry)=%" PRIsz "\n", sizeof(struct entry));
+    
+    if (argc != 6) {
+        fprintf(stderr, "Usage: %s <num_keys> <num_lookups> <num_remove> <do_remove=1/0> <do_verify=1/0>\n", (argc > 0 ? argv[0] : ""));
+        return 1;
+    }
+    
+    size_t num_keys = atoi(argv[1]);
+    size_t num_lookups = atoi(argv[2]);
+    size_t num_remove = atoi(argv[3]);
+    size_t do_remove = atoi(argv[4]);
+    size_t do_verify = atoi(argv[5]);
+    
+    printf("Allocating keys...\n");
+    entry_key *keys = (entry_key *)BAllocArray(num_keys, sizeof(keys[0]));
+    ASSERT_FORCE(keys);
+
+    printf("Generating random keys...\n");
+    random_bytes((char *)keys, num_keys * sizeof(keys[0]));
+    
+    printf("Allocating lookup indices...\n");
+    uint64_t *lookup_indices = (uint64_t *)BAllocArray(num_lookups, sizeof(lookup_indices[0]));
+    ASSERT_FORCE(lookup_indices);
+    
+    printf("Generating random lookup indices...\n");
+    random_bytes((char *)lookup_indices, num_lookups * sizeof(lookup_indices[0]));
+    
+    printf("Allocating remove indices...\n");
+    uint64_t *remove_indices = (uint64_t *)BAllocArray(num_remove, sizeof(remove_indices[0]));
+    ASSERT_FORCE(remove_indices);
+    
+    printf("Generating random remove indices...\n");
+    random_bytes((char *)remove_indices, num_remove * sizeof(remove_indices[0]));
+    
+#if USE_ASSOC
+    printf("Allocating assoc values...\n");
+    assoc_value *assoc_values = (assoc_value *)BAllocArray(num_keys, sizeof(assoc_values[0]));
+    ASSERT_FORCE(assoc_values);
+
+    printf("Generating random assoc values...\n");
+    random_bytes((char *)assoc_values, num_keys * sizeof(assoc_values[0]));
+#endif
+    
+    printf("Allocating entries...\n");
+    ASSERT_FORCE(num_keys <= MAX_INDICES);
+    struct entry *entries = (struct entry *)BAllocArray(num_keys, sizeof(*entries));
+    ASSERT_FORCE(entries);
+    entry_index num_used_entries = 0;
+    
+    MyTree tree;
+    MyTree_Init(&tree);
+    
+    struct entry *arg = entries;
+    
+    ASSERT_FORCE(MyTree_IsEmpty(&tree));
+#if USE_COUNTS
+    ASSERT_FORCE(MyTree_Count(&tree, arg) == 0);
+#endif
+    test_assoc(&tree, arg);
+    
+    size_t num;
+#if USE_COUNTS
+    size_t prevNum;
+#endif
+    
+    printf("Inserting random numbers...\n");
+    num = 0;
+    for (size_t i = 0; i < num_keys; i++) {
+        entries[num_used_entries].key = keys[i];
+#if USE_ASSOC
+        entries[num_used_entries].assoc_value = assoc_values[i];
+#endif
+        MyTreeRef ref = {&entries[num_used_entries], num_used_entries};
+        if (!MyTree_Insert(&tree, arg, ref, NULL)) {
+            //printf("Insert collision!\n");
+            continue;
+        }
+        num_used_entries++;
+        num++;
+    }
+    printf("Inserted %" PRIsz ".\n", num);
+#if USE_COUNTS
+    ASSERT_FORCE(MyTree_Count(&tree, arg) == num);
+#endif
+    if (do_verify) {
+        printf("Verifying...\n");
+        MyTree_Verify(&tree, arg);
+        test_assoc(&tree, arg);
+    }
+    
+    printf("Looking up random inserted keys...\n");
+    for (size_t i = 0; i < num_lookups; i++) {
+        entry_index idx = lookup_indices[i] % num_keys;
+        MyTreeRef entry = MyTree_LookupExact(&tree, arg, keys[idx]);
+        ASSERT_FORCE(!MyTreeIsNullRef(entry));
+    }
+    
+#if USE_COUNTS
+    prevNum = MyTree_Count(&tree, arg);
+#endif
+    num = 0;
+    printf("Looking up and removing random inserted keys...\n");
+    for (size_t i = 0; i < num_remove; i++) {
+        entry_index idx = remove_indices[i] % num_keys;
+        MyTreeRef entry = MyTree_LookupExact(&tree, arg, keys[idx]);
+        if (MyTreeIsNullRef(entry)) {
+            //printf("Remove collision!\n");
+            continue;
+        }
+        ASSERT_FORCE(entry.ptr->key == keys[idx]);
+        MyTree_Remove(&tree, arg, entry);
+        num++;
+    }
+    printf("Removed %" PRIsz ".\n", num);
+#if USE_COUNTS
+    ASSERT_FORCE(MyTree_Count(&tree, arg) == prevNum - num);
+#endif
+    if (do_verify) {
+        printf("Verifying...\n");
+        MyTree_Verify(&tree, arg);
+        test_assoc(&tree, arg);
+    }
+    
+    if (do_remove) {
+#if USE_COUNTS
+        prevNum = MyTree_Count(&tree, arg);
+#endif
+        num = 0;
+        printf("Removing remaining...\n");
+        
+        MyTreeRef cur = MyTree_GetFirst(&tree, arg);
+        while (!MyTreeIsNullRef(cur)) {
+            MyTreeRef prev = cur;
+            cur = MyTree_GetNext(&tree, arg, cur);
+            MyTree_Remove(&tree, arg, prev);
+            num++;
+        }
+        
+        printf("Removed %" PRIsz ".\n", num);
+        ASSERT_FORCE(MyTree_IsEmpty(&tree));
+#if USE_COUNTS
+        ASSERT_FORCE(MyTree_Count(&tree, arg) == 0);
+        ASSERT_FORCE(num == prevNum);
+#endif
+        if (do_verify) {
+            printf("Verifying...\n");
+            MyTree_Verify(&tree, arg);
+        }
+    }
+    
+    printf("Freeing...\n");
+    BFree(keys);
+    BFree(lookup_indices);
+    BFree(remove_indices);
+#if USE_ASSOC
+    BFree(assoc_values);
+#endif
+    BFree(entries);
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/cavl_test_tree.h b/external/badvpn_dns/examples/cavl_test_tree.h
new file mode 100644
index 0000000..463076f
--- /dev/null
+++ b/external/badvpn_dns/examples/cavl_test_tree.h
@@ -0,0 +1,23 @@
+#define CAVL_PARAM_NAME MyTree
+#define CAVL_PARAM_FEATURE_COUNTS USE_COUNTS
+#define CAVL_PARAM_FEATURE_KEYS_ARE_INDICES 0
+#define CAVL_PARAM_FEATURE_ASSOC USE_ASSOC
+#define CAVL_PARAM_TYPE_ENTRY struct entry
+#define CAVL_PARAM_TYPE_LINK entry_index
+#define CAVL_PARAM_TYPE_KEY entry_key
+#define CAVL_PARAM_TYPE_ARG entry_ptr
+#define CAVL_PARAM_TYPE_COUNT size_t
+#define CAVL_PARAM_TYPE_ASSOC assoc_sum
+#define CAVL_PARAM_VALUE_COUNT_MAX SIZE_MAX
+#define CAVL_PARAM_VALUE_NULL ((entry_index)-1)
+#define CAVL_PARAM_VALUE_ASSOC_ZERO 0
+#define CAVL_PARAM_FUN_DEREF(arg, link) (&(arg)[(link)])
+#define CAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) B_COMPARE((entry1).ptr->key, (entry2).ptr->key)
+#define CAVL_PARAM_FUN_COMPARE_KEY_ENTRY(arg, key1, entry2) B_COMPARE((key1), (entry2).ptr->key)
+#define CAVL_PARAM_FUN_ASSOC_VALUE(arg, entry) ((entry).ptr->assoc_value)
+#define CAVL_PARAM_FUN_ASSOC_OPER(arg, value1, value2) ((value1) + (value2))
+#define CAVL_PARAM_MEMBER_CHILD tree_child
+#define CAVL_PARAM_MEMBER_BALANCE tree_balance
+#define CAVL_PARAM_MEMBER_PARENT tree_parent
+#define CAVL_PARAM_MEMBER_COUNT tree_count
+#define CAVL_PARAM_MEMBER_ASSOC assoc_sum
diff --git a/external/badvpn_dns/examples/dhcpclient_test.c b/external/badvpn_dns/examples/dhcpclient_test.c
new file mode 100644
index 0000000..9601c01
--- /dev/null
+++ b/external/badvpn_dns/examples/dhcpclient_test.c
@@ -0,0 +1,159 @@
+/**
+ * @file dhcpclient_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <system/BTime.h>
+#include <system/BNetwork.h>
+#include <dhcpclient/BDHCPClient.h>
+
+BReactor reactor;
+BRandom2 random2;
+BDHCPClient dhcp;
+
+static void signal_handler (void *user);
+static void dhcp_handler (void *unused, int event);
+
+int main (int argc, char **argv)
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    if (argc != 2) {
+        printf("Usage: %s <interface>\n", argv[0]);
+        goto fail0;
+    }
+    
+    char *ifname = argv[1];
+    
+    BTime_Init();
+    
+    BLog_InitStdout();
+    
+    if (!BNetwork_GlobalInit()) {
+        DEBUG("BNetwork_GlobalInit failed");
+        goto fail1;
+    }
+    
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        goto fail1;
+    }
+    
+    if (!BRandom2_Init(&random2, 0)) {
+        DEBUG("BRandom2_Init failed");
+        goto fail1a;
+    }
+    
+    if (!BSignal_Init(&reactor, signal_handler, NULL)) {
+        DEBUG("BSignal_Init failed");
+        goto fail2;
+    }
+    
+    struct BDHCPClient_opts opts = {};
+    
+    if (!BDHCPClient_Init(&dhcp, ifname, opts, &reactor, &random2, dhcp_handler, NULL)) {
+        DEBUG("BDHCPClient_Init failed");
+        goto fail3;
+    }
+    
+    BReactor_Exec(&reactor);
+    
+    BDHCPClient_Free(&dhcp);
+fail3:
+    BSignal_Finish();
+fail2:
+    BRandom2_Free(&random2);
+fail1a:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return 1;
+}
+
+void signal_handler (void *user)
+{
+    DEBUG("termination requested");
+    
+    BReactor_Quit(&reactor, 0);
+}
+
+void dhcp_handler (void *unused, int event)
+{
+    switch (event) {
+        case BDHCPCLIENT_EVENT_UP: {
+            printf("DHCP: up");
+            
+            uint32_t ip;
+            uint8_t *ipb = (void *)&ip;
+            
+            BDHCPClient_GetClientIP(&dhcp, &ip);
+            printf(" IP=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, ipb[0], ipb[1], ipb[2], ipb[3]);
+            
+            BDHCPClient_GetClientMask(&dhcp, &ip);
+            printf(" Mask=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, ipb[0], ipb[1], ipb[2], ipb[3]);
+            
+            if (BDHCPClient_GetRouter(&dhcp, &ip)) {
+                printf(" Router=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, ipb[0], ipb[1], ipb[2], ipb[3]);
+            }
+            
+            uint32_t dns[BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS];
+            int num = BDHCPClient_GetDNS(&dhcp, dns, BDHCPCLIENT_MAX_DOMAIN_NAME_SERVERS);
+            for (int i = 0; i < num; i++) {
+                ip=dns[i];
+                printf(" DNS=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, ipb[0], ipb[1], ipb[2], ipb[3]);
+            }
+            
+            printf("\n");
+        } break;
+        
+        case BDHCPCLIENT_EVENT_DOWN: {
+            printf("DHCP: down\n");
+        } break;
+        
+        case BDHCPCLIENT_EVENT_ERROR: {
+            printf("DHCP: error\n");
+            
+            // exit reactor
+            BReactor_Quit(&reactor, 0);
+        } break;
+        
+        default:
+            ASSERT(0);
+    }
+}
diff --git a/external/badvpn_dns/examples/emscripten_test.c b/external/badvpn_dns/examples/emscripten_test.c
new file mode 100644
index 0000000..52b0351
--- /dev/null
+++ b/external/badvpn_dns/examples/emscripten_test.c
@@ -0,0 +1,71 @@
+/**
+ * @file emscripten_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <emscripten/emscripten.h>
+
+#include <misc/debug.h>
+#include <system/BTime.h>
+#include <system/BReactor.h>
+
+BReactor reactor;
+BTimer timer;
+BPending job;
+
+static void timer_handler (void *unused)
+{
+    printf("timer_handler %"PRIu64"\n", btime_gettime());
+    
+    BPending_Set(&job);
+    BReactor_SetTimer(&reactor, &timer);
+}
+
+static void job_handler (void *unused)
+{
+    printf("job_handler %"PRIu64"\n", btime_gettime());
+}
+
+int main ()
+{
+    BTime_Init();
+    
+    BReactor_EmscriptenInit(&reactor);
+    
+    BTimer_Init(&timer, 500, timer_handler, NULL);
+    BReactor_SetTimer(&reactor, &timer);
+    
+    BPending_Init(&job, BReactor_PendingGroup(&reactor), job_handler, NULL);
+    BPending_Set(&job);
+    
+    BReactor_EmscriptenSync(&reactor);
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/fairqueue_test.c b/external/badvpn_dns/examples/fairqueue_test.c
new file mode 100644
index 0000000..482e086
--- /dev/null
+++ b/external/badvpn_dns/examples/fairqueue_test.c
@@ -0,0 +1,145 @@
+/**
+ * @file fairqueue_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+
+#include <misc/debug.h>
+#include <system/BReactor.h>
+#include <base/BLog.h>
+#include <system/BTime.h>
+#include <flow/PacketPassFairQueue.h>
+#include <examples/FastPacketSource.h>
+#include <examples/TimerPacketSink.h>
+
+#define OUTPUT_INTERVAL 0
+#define REMOVE_INTERVAL 1
+#define NUM_INPUTS 3
+
+BReactor reactor;
+TimerPacketSink sink;
+PacketPassFairQueue fq;
+PacketPassFairQueueFlow flows[NUM_INPUTS];
+FastPacketSource sources[NUM_INPUTS];
+char *data[] = {"0 data", "1 datadatadata", "2 datadatadatadatadata"};
+BTimer timer;
+int current_cancel;
+
+static void init_input (int i)
+{
+    PacketPassFairQueueFlow_Init(&flows[i], &fq);
+    FastPacketSource_Init(&sources[i], PacketPassFairQueueFlow_GetInput(&flows[i]), (uint8_t *)data[i], strlen(data[i]), BReactor_PendingGroup(&reactor));
+}
+
+static void free_input (int i)
+{
+    FastPacketSource_Free(&sources[i]);
+    PacketPassFairQueueFlow_Free(&flows[i]);
+}
+
+static void reset_input (void)
+{
+    PacketPassFairQueueFlow_AssertFree(&flows[current_cancel]);
+    
+    printf("removing %d\n", current_cancel);
+    
+    // remove flow
+    free_input(current_cancel);
+    
+    // init flow
+    init_input(current_cancel);
+    
+    // increment cancel
+    current_cancel = (current_cancel + 1) % NUM_INPUTS;
+    
+    // reset timer
+    BReactor_SetTimer(&reactor, &timer);
+}
+
+static void flow_handler_busy (void *user)
+{
+    PacketPassFairQueueFlow_AssertFree(&flows[current_cancel]);
+    
+    reset_input();
+}
+
+static void timer_handler (void *user)
+{
+    // if flow is busy, request cancel and wait for it
+    if (PacketPassFairQueueFlow_IsBusy(&flows[current_cancel])) {
+        printf("cancelling %d\n", current_cancel);
+        PacketPassFairQueueFlow_RequestCancel(&flows[current_cancel]);
+        PacketPassFairQueueFlow_SetBusyHandler(&flows[current_cancel], flow_handler_busy, NULL);
+        return;
+    }
+    
+    reset_input();
+}
+
+int main ()
+{
+    // initialize logging
+    BLog_InitStdout();
+    
+    // init time
+    BTime_Init();
+    
+    // initialize reactor
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        return 1;
+    }
+    
+    // initialize sink
+    TimerPacketSink_Init(&sink, &reactor, 500, OUTPUT_INTERVAL);
+    
+    // initialize queue
+    if (!PacketPassFairQueue_Init(&fq, TimerPacketSink_GetInput(&sink), BReactor_PendingGroup(&reactor), 1, 1)) {
+        DEBUG("PacketPassFairQueue_Init failed");
+        return 1;
+    }
+    
+    // initialize inputs
+    for (int i = 0; i < NUM_INPUTS; i++) {
+        init_input(i);
+    }
+    
+    // init cancel timer
+    BTimer_Init(&timer, REMOVE_INTERVAL, timer_handler, NULL);
+    BReactor_SetTimer(&reactor, &timer);
+    
+    // init cancel counter
+    current_cancel = 0;
+    
+    // run reactor
+    int ret = BReactor_Exec(&reactor);
+    BReactor_Free(&reactor);
+    return ret;
+}
diff --git a/external/badvpn_dns/examples/fairqueue_test2.c b/external/badvpn_dns/examples/fairqueue_test2.c
new file mode 100644
index 0000000..0fe9d34
--- /dev/null
+++ b/external/badvpn_dns/examples/fairqueue_test2.c
@@ -0,0 +1,93 @@
+/**
+ * @file fairqueue_test2.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include <misc/debug.h>
+#include <system/BReactor.h>
+#include <base/BLog.h>
+#include <system/BTime.h>
+#include <flow/PacketPassFairQueue.h>
+#include <examples/FastPacketSource.h>
+#include <examples/RandomPacketSink.h>
+
+#define SINK_TIMER 0
+
+int main ()
+{
+    // initialize logging
+    BLog_InitStdout();
+    
+    // init time
+    BTime_Init();
+    
+    // initialize reactor
+    BReactor reactor;
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        return 1;
+    }
+    
+    // initialize sink
+    RandomPacketSink sink;
+    RandomPacketSink_Init(&sink, &reactor, 500, SINK_TIMER);
+    
+    // initialize queue
+    PacketPassFairQueue fq;
+    if (!PacketPassFairQueue_Init(&fq, RandomPacketSink_GetInput(&sink), BReactor_PendingGroup(&reactor), 0, 1)) {
+        DEBUG("PacketPassFairQueue_Init failed");
+        return 1;
+    }
+    
+    // initialize source 1
+    PacketPassFairQueueFlow flow1;
+    PacketPassFairQueueFlow_Init(&flow1, &fq);
+    FastPacketSource source1;
+    char data1[] = "data1";
+    FastPacketSource_Init(&source1, PacketPassFairQueueFlow_GetInput(&flow1), (uint8_t *)data1, strlen(data1), BReactor_PendingGroup(&reactor));
+    
+    // initialize source 2
+    PacketPassFairQueueFlow flow2;
+    PacketPassFairQueueFlow_Init(&flow2, &fq);
+    FastPacketSource source2;
+    char data2[] = "data2data2";
+    FastPacketSource_Init(&source2, PacketPassFairQueueFlow_GetInput(&flow2), (uint8_t *)data2, strlen(data2), BReactor_PendingGroup(&reactor));
+    
+    // initialize source 3
+    PacketPassFairQueueFlow flow3;
+    PacketPassFairQueueFlow_Init(&flow3, &fq);
+    FastPacketSource source3;
+    char data3[] = "data3data3data3data3data3data3data3data3data3";
+    FastPacketSource_Init(&source3, PacketPassFairQueueFlow_GetInput(&flow3), (uint8_t *)data3, strlen(data3), BReactor_PendingGroup(&reactor));
+    
+    // run reactor
+    int ret = BReactor_Exec(&reactor);
+    BReactor_Free(&reactor);
+    return ret;
+}
diff --git a/external/badvpn_dns/examples/indexedlist_test.c b/external/badvpn_dns/examples/indexedlist_test.c
new file mode 100644
index 0000000..d5282c0
--- /dev/null
+++ b/external/badvpn_dns/examples/indexedlist_test.c
@@ -0,0 +1,95 @@
+/**
+ * @file indexedlist_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <structure/IndexedList.h>
+
+IndexedList il;
+
+struct elem {
+    int value;
+    IndexedListNode node;
+};
+
+static void elem_insert (struct elem *e, int value, uint64_t index)
+{
+    e->value = value;
+    IndexedList_InsertAt(&il, &e->node, index);
+}
+
+static void remove_at (uint64_t index)
+{
+    IndexedListNode *n = IndexedList_GetAt(&il, index);
+    struct elem *e = UPPER_OBJECT(n, struct elem, node);
+    IndexedList_Remove(&il, &e->node);
+}
+
+static void print_list (void)
+{
+    for (uint64_t i = 0; i < IndexedList_Count(&il); i++) {
+        IndexedListNode *n = IndexedList_GetAt(&il, i);
+        struct elem *e = UPPER_OBJECT(n, struct elem, node);
+        printf("%d ", e->value);
+    }
+    printf("\n");
+}
+
+int main (int argc, char *argv[])
+{
+    IndexedList_Init(&il);
+    
+    struct elem arr[100];
+    
+    print_list();
+    
+    elem_insert(&arr[0], 1, 0);
+    print_list();
+    elem_insert(&arr[1], 2, 0);
+    print_list();
+    elem_insert(&arr[2], 3, 0);
+    print_list();
+    elem_insert(&arr[3], 4, 0);
+    print_list();
+    elem_insert(&arr[4], 5, 0);
+    print_list();
+    elem_insert(&arr[5], 6, 0);
+    print_list();
+    
+    elem_insert(&arr[6], 7, 1);
+    print_list();
+    
+    remove_at(0);
+    print_list();
+    
+    remove_at(5);
+    print_list();
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/ipaddr6_test.c b/external/badvpn_dns/examples/ipaddr6_test.c
new file mode 100644
index 0000000..7da486d
--- /dev/null
+++ b/external/badvpn_dns/examples/ipaddr6_test.c
@@ -0,0 +1,169 @@
+/**
+ * @file ipaddr6_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <misc/debug.h>
+#include <misc/ipaddr6.h>
+
+#define PRINT_TEST(addr_bytes, string) \
+    { \
+        struct ipv6_addr addr = {addr_bytes}; \
+        char str[IPADDR6_PRINT_MAX]; \
+        ipaddr6_print_addr(addr, str); \
+        ASSERT_FORCE(!strcmp(str, (string))); \
+        struct ipv6_addr parsed_addr; \
+        int res = ipaddr6_parse_ipv6_addr_bin(str, strlen(str), &parsed_addr); \
+        ASSERT_FORCE(res); \
+        ASSERT_FORCE(!memcmp(parsed_addr.bytes, addr.bytes, 16)); \
+    }
+
+#define PARSE_TEST(string, addr_bytes) \
+    { \
+        struct ipv6_addr exp_addr = {addr_bytes}; \
+        struct ipv6_addr addr; \
+        int res = ipaddr6_parse_ipv6_addr_bin((string), strlen((string)), &addr); \
+        ASSERT_FORCE(res); \
+        ASSERT_FORCE(!memcmp(addr.bytes, exp_addr.bytes, 16)); \
+    }
+
+#define PARSE_FAIL_TEST(string) \
+    { \
+        struct ipv6_addr addr; \
+        int res = ipaddr6_parse_ipv6_addr_bin((string), strlen((string)), &addr); \
+        ASSERT_FORCE(!res); \
+    }
+
+#define MASK_TEST(mask_bytes, prefix) \
+    { \
+        struct ipv6_addr mask = {mask_bytes}; \
+        int parsed_prefix; \
+        int res = ipaddr6_ipv6_prefix_from_mask(mask, &parsed_prefix); \
+        ASSERT_FORCE(res); \
+        ASSERT_FORCE(parsed_prefix == (prefix)); \
+        struct ipv6_addr generated_mask; \
+        ipaddr6_ipv6_mask_from_prefix(parsed_prefix, &generated_mask); \
+        ASSERT_FORCE(!memcmp(generated_mask.bytes, mask.bytes, 16)); \
+    }
+
+#define PASS(...) __VA_ARGS__
+
+int main ()
+{
+    PRINT_TEST(PASS({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}), "::1")
+    PRINT_TEST(PASS({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), "::")
+    PRINT_TEST(PASS({0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}),"2001:db8::1")
+    PRINT_TEST(PASS({0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01}), "2001:db8::2:1")
+    PRINT_TEST(PASS({0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01}), "2001:db8:0:1:1:1:1:1")
+    PRINT_TEST(PASS({0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01}), "2001:db8:0:1:1:1:1:1")
+    PRINT_TEST(PASS({0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}), "2001:db8::1:0:0:1")
+    
+    PARSE_TEST("::", PASS({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}))
+    PARSE_TEST("::1", PASS({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}))
+    PARSE_TEST("::abcd", PASS({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xcd}))
+    PARSE_TEST("::0123:abcd", PASS({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x23, 0xab, 0xcd}))
+    PARSE_TEST("abcd::", PASS({0xab, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}))
+    PARSE_TEST("abcd:0123::", PASS({0xab, 0xcd, 0x01, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}))
+    PARSE_TEST("1::2", PASS({0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}))
+    PARSE_TEST("abcd:0123::3210:dcba", PASS({0xab, 0xcd, 0x01, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x10, 0xdc, 0xba}))
+    PARSE_TEST("4567:abcd:0123::3210:dcba", PASS({0x45, 0x67, 0xab, 0xcd, 0x01, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x10, 0xdc, 0xba}))
+    PARSE_TEST("4567:abcd:0123:1111:2222:3333:3210::", PASS({0x45, 0x67, 0xab, 0xcd, 0x01, 0x23, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x32, 0x10, 0x00, 0x00}))
+    PARSE_TEST("::4567:abcd:0123:1111:2222:3333:3210", PASS({0x00, 0x00, 0x45, 0x67, 0xab, 0xcd, 0x01, 0x23, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x32, 0x10}))
+    PARSE_TEST("4567:abcd:0123:1111:2222:3333:3210:dcba", PASS({0x45, 0x67, 0xab, 0xcd, 0x01, 0x23, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x32, 0x10, 0xdc, 0xba}))
+    PARSE_TEST("04567:000abcd:00000123:01111:2222:03333:0003210:0dcba", PASS({0x45, 0x67, 0xab, 0xcd, 0x01, 0x23, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x32, 0x10, 0xdc, 0xba}))
+    PARSE_TEST("::1.2.3.4", PASS({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}))
+    PARSE_TEST("ff::1.2.3.4", PASS({0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}))
+    PARSE_TEST("ff::0.2.3.4", PASS({0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04}))
+    PARSE_TEST("1:2:3:4:5:6:1.2.3.4", PASS({0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x01, 0x02, 0x03, 0x04}))
+    PARSE_TEST("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255", PASS({0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}))
+    PARSE_TEST("1::fffa:1.2.3.4", PASS({0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfa, 0x01, 0x02, 0x03, 0x04}))
+    PARSE_TEST("1::fffa:0.0.0.0", PASS({0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfa, 0x00, 0x00, 0x00, 0x00}))
+    
+    PARSE_FAIL_TEST("")
+    PARSE_FAIL_TEST(":")
+    PARSE_FAIL_TEST("a")
+    PARSE_FAIL_TEST("a:b")
+    PARSE_FAIL_TEST(":b")
+    PARSE_FAIL_TEST("b:")
+    PARSE_FAIL_TEST("1:2:3:4:5:6:7")
+    PARSE_FAIL_TEST(":1:2:3:4:5:6:7")
+    PARSE_FAIL_TEST("1:2:3:4:5:6:7:")
+    PARSE_FAIL_TEST(":::")
+    PARSE_FAIL_TEST("::a::")
+    PARSE_FAIL_TEST("::a::b")
+    PARSE_FAIL_TEST("c::a::b")
+    PARSE_FAIL_TEST("c::a::")
+    PARSE_FAIL_TEST("10000::")
+    PARSE_FAIL_TEST("1:2:3:4:5:6:7:8:9")
+    PARSE_FAIL_TEST("1:2:3:4::5:6:7:8:9")
+    PARSE_FAIL_TEST("::1:2:3:4:5:6:7:8:9")
+    PARSE_FAIL_TEST("1:2:3:4:5:6:7:8:9::")
+    PARSE_FAIL_TEST("a::b:")
+    PARSE_FAIL_TEST(":a::b")
+    PARSE_FAIL_TEST("::g")
+    PARSE_FAIL_TEST("::1.2")
+    PARSE_FAIL_TEST("::1.2.3.4.5")
+    PARSE_FAIL_TEST("::01.2.3.4")
+    PARSE_FAIL_TEST("::1.2.3.04")
+    PARSE_FAIL_TEST("::1.2.3.256")
+    PARSE_FAIL_TEST("1.2.3.4")
+    PARSE_FAIL_TEST("::8259.2.473.256")
+    PARSE_FAIL_TEST("1:2:3:4:5:6:7:1.2.3.4")
+    PARSE_FAIL_TEST("1:2:3:4:5:1.2.3.4")
+    PARSE_FAIL_TEST("::1.2.3.4::")
+    PARSE_FAIL_TEST("::1.2.3.4:1")
+    PARSE_FAIL_TEST("localhost6")
+    
+    MASK_TEST(PASS({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 0)
+    MASK_TEST(PASS({0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 1)
+    MASK_TEST(PASS({0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 2)
+    MASK_TEST(PASS({0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 3)
+    MASK_TEST(PASS({0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 4)
+    MASK_TEST(PASS({0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 5)
+    MASK_TEST(PASS({0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 6)
+    MASK_TEST(PASS({0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 7)
+    MASK_TEST(PASS({0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 8)
+    
+    MASK_TEST(PASS({0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 9)
+    MASK_TEST(PASS({0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 10)
+    MASK_TEST(PASS({0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 11)
+    MASK_TEST(PASS({0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 12)
+    MASK_TEST(PASS({0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 13)
+    MASK_TEST(PASS({0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 14)
+    MASK_TEST(PASS({0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 15)
+    MASK_TEST(PASS({0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), 16)
+    
+    MASK_TEST(PASS({0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE}), 127)
+    MASK_TEST(PASS({0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), 128)
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/ncd_parser_test.c b/external/badvpn_dns/examples/ncd_parser_test.c
new file mode 100644
index 0000000..ac913b5
--- /dev/null
+++ b/external/badvpn_dns/examples/ncd_parser_test.c
@@ -0,0 +1,294 @@
+/**
+ * @file ncd_parser_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <misc/debug.h>
+#include <misc/expstring.h>
+#include <base/BLog.h>
+#include <ncd/NCDConfigParser.h>
+#include <ncd/NCDValGenerator.h>
+#include <ncd/NCDSugar.h>
+
+static int generate_val (NCDValue *value, ExpString *out_str)
+{
+    switch (NCDValue_Type(value)) {
+        case NCDVALUE_STRING: {
+            const char *str = NCDValue_StringValue(value);
+            size_t len = NCDValue_StringLength(value);
+            
+            if (!ExpString_AppendChar(out_str, '"')) {
+                goto fail;
+            }
+            
+            for (size_t i = 0; i < len; i++) {
+                if (str[i] == '\0') {
+                    char buf[5];
+                    snprintf(buf, sizeof(buf), "\\x%02"PRIx8, (uint8_t)str[i]);
+                    
+                    if (!ExpString_Append(out_str, buf)) {
+                        goto fail;
+                    }
+                    
+                    continue;
+                }
+                
+                if (str[i] == '"' || str[i] == '\\') {
+                    if (!ExpString_AppendChar(out_str, '\\')) {
+                        goto fail;
+                    }
+                }
+                
+                if (!ExpString_AppendChar(out_str, str[i])) {
+                    goto fail;
+                }
+            }
+            
+            if (!ExpString_AppendChar(out_str, '"')) {
+                goto fail;
+            }
+        } break;
+        
+        case NCDVALUE_LIST: {
+            if (!ExpString_AppendChar(out_str, '{')) {
+                goto fail;
+            }
+            
+            int is_first = 1;
+            
+            for (NCDValue *e = NCDValue_ListFirst(value); e; e = NCDValue_ListNext(value, e)) {
+                if (!is_first) {
+                    if (!ExpString_Append(out_str, ", ")) {
+                        goto fail;
+                    }
+                }
+                
+                if (!generate_val(e, out_str)) {
+                    goto fail;
+                }
+                
+                is_first = 0;
+            }
+            
+            if (!ExpString_AppendChar(out_str, '}')) {
+                goto fail;
+            }
+        } break;
+        
+        case NCDVALUE_MAP: {
+            if (!ExpString_AppendChar(out_str, '[')) {
+                goto fail;
+            }
+            
+            int is_first = 1;
+            
+            for (NCDValue *ekey = NCDValue_MapFirstKey(value); ekey; ekey = NCDValue_MapNextKey(value, ekey)) {
+                NCDValue *eval = NCDValue_MapKeyValue(value, ekey);
+                
+                if (!is_first) {
+                    if (!ExpString_Append(out_str, ", ")) {
+                        goto fail;
+                    }
+                }
+                
+                if (!generate_val(ekey, out_str)) {
+                    goto fail;
+                }
+                
+                if (!ExpString_AppendChar(out_str, ':')) {
+                    goto fail;
+                }
+                
+                if (!generate_val(eval, out_str)) {
+                    goto fail;
+                }
+                
+                is_first = 0;
+            }
+            
+            if (!ExpString_AppendChar(out_str, ']')) {
+                goto fail;
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+static void print_indent (unsigned int indent)
+{
+    while (indent > 0) {
+        printf("  ");
+        indent--;
+    }
+}
+
+static void print_value (NCDValue *v, unsigned int indent)
+{
+    ExpString estr;
+    if (!ExpString_Init(&estr)) {
+        DEBUG("ExpString_Init failed");
+        exit(1);
+    }
+    
+    if (!generate_val(v, &estr)) {
+        DEBUG("generate_val failed");
+        exit(1);
+    }
+    
+    print_indent(indent);
+    printf("%s\n", ExpString_Get(&estr));
+    
+    ExpString_Free(&estr);
+}
+
+static void print_block (NCDBlock *block, unsigned int indent)
+{
+    for (NCDStatement *st = NCDBlock_FirstStatement(block); st; st = NCDBlock_NextStatement(block, st)) {
+        const char *name = NCDStatement_Name(st) ? NCDStatement_Name(st) : "";
+        
+        switch (NCDStatement_Type(st)) {
+            case NCDSTATEMENT_REG: {
+                const char *objname = NCDStatement_RegObjName(st) ? NCDStatement_RegObjName(st) : "";
+                const char *cmdname = NCDStatement_RegCmdName(st);
+                
+                print_indent(indent);
+                printf("reg name=%s objname=%s cmdname=%s args:\n", name, objname, cmdname);
+                
+                print_value(NCDStatement_RegArgs(st), indent + 2);
+            } break;
+            
+            case NCDSTATEMENT_IF: {
+                print_indent(indent);
+                printf("if name=%s\n", name);
+                
+                NCDIfBlock *ifb = NCDStatement_IfBlock(st);
+                
+                for (NCDIf *ifc = NCDIfBlock_FirstIf(ifb); ifc; ifc = NCDIfBlock_NextIf(ifb, ifc)) {
+                    print_indent(indent + 2);
+                    printf("if\n");
+                    
+                    print_value(NCDIf_Cond(ifc), indent + 4);
+                    
+                    print_indent(indent + 2);
+                    printf("then\n");
+                    
+                    print_block(NCDIf_Block(ifc), indent + 4);
+                }
+                
+                if (NCDStatement_IfElse(st)) {
+                    print_indent(indent + 2);
+                    printf("else\n");
+                    
+                    print_block(NCDStatement_IfElse(st), indent + 4);
+                }
+            } break;
+            
+            case NCDSTATEMENT_FOREACH: {
+                const char *name1 = NCDStatement_ForeachName1(st);
+                const char *name2 = NCDStatement_ForeachName2(st) ? NCDStatement_ForeachName2(st) : "";
+                
+                print_indent(indent);
+                printf("foreach name=%s name1=%s name2=%s\n", name, name1, name2);
+                
+                print_block(NCDStatement_ForeachBlock(st), indent + 2);
+            } break;
+            
+            default: ASSERT(0);
+        }
+    }
+}
+
+int main (int argc, char **argv)
+{
+    int res = 1;
+    
+    if (argc != 3) {
+        printf("Usage: %s <desugar=0/1> <string>\n", (argc > 0 ? argv[0] : ""));
+        goto fail0;
+    }
+    
+    int desugar = atoi(argv[1]);
+    char *text = argv[2];
+    
+    BLog_InitStdout();
+    
+    // parse
+    NCDProgram prog;
+    if (!NCDConfigParser_Parse(text, strlen(text), &prog)) {
+        DEBUG("NCDConfigParser_Parse failed");
+        goto fail1;
+    }
+    
+    // desugar
+    if (desugar) {
+        if (!NCDSugar_Desugar(&prog)) {
+            DEBUG("NCDSugar_Desugar failed");
+            goto fail2;
+        }
+    }
+    
+    // print
+    for (NCDProgramElem *elem = NCDProgram_FirstElem(&prog); elem; elem = NCDProgram_NextElem(&prog, elem)) {
+        switch (NCDProgramElem_Type(elem)) {
+            case NCDPROGRAMELEM_PROCESS: {
+                NCDProcess *p = NCDProgramElem_Process(elem);
+                printf("process name=%s is_template=%d\n", NCDProcess_Name(p), NCDProcess_IsTemplate(p));
+                print_block(NCDProcess_Block(p), 2);
+            } break;
+            
+            case NCDPROGRAMELEM_INCLUDE: {
+                printf("include path=%s\n", NCDProgramElem_IncludePathData(elem));
+            } break;
+            
+            case NCDPROGRAMELEM_INCLUDE_GUARD: {
+                printf("include_guard id=%s\n", NCDProgramElem_IncludeGuardIdData(elem));
+            } break;
+            
+            default: ASSERT(0);
+        }
+    }
+    
+    res = 0;
+fail2:
+    NCDProgram_Free(&prog);
+fail1:
+    BLog_Free();
+fail0:
+    return res;
+}
diff --git a/external/badvpn_dns/examples/ncd_tokenizer_test.c b/external/badvpn_dns/examples/ncd_tokenizer_test.c
new file mode 100644
index 0000000..84f90eb
--- /dev/null
+++ b/external/badvpn_dns/examples/ncd_tokenizer_test.c
@@ -0,0 +1,149 @@
+/**
+ * @file ncd_tokenizer_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <base/BLog.h>
+#include <ncd/NCDConfigTokenizer.h>
+
+int error;
+
+static int tokenizer_output (void *user, int token, char *value, size_t value_len, size_t line, size_t line_char)
+{
+    if (token == NCD_ERROR) {
+        printf("line %zu, character %zu: tokenizer error\n", line, line_char);
+        error = 1;
+        return 0;
+    }
+    
+    switch (token) {
+        case NCD_EOF:
+            printf("eof\n");
+            break;
+        case NCD_TOKEN_CURLY_OPEN:
+            printf("curly_open\n");
+            break;
+        case NCD_TOKEN_CURLY_CLOSE:
+            printf("curly_close\n");
+            break;
+        case NCD_TOKEN_ROUND_OPEN:
+            printf("round_open\n");
+            break;
+        case NCD_TOKEN_ROUND_CLOSE:
+            printf("round_close\n");
+            break;
+        case NCD_TOKEN_SEMICOLON:
+            printf("semicolon\n");
+            break;
+        case NCD_TOKEN_DOT:
+            printf("dot\n");
+            break;
+        case NCD_TOKEN_COMMA:
+            printf("comma\n");
+            break;
+        case NCD_TOKEN_PROCESS:
+            printf("process\n");
+            break;
+        case NCD_TOKEN_NAME:
+            printf("name %s\n", value);
+            free(value);
+            break;
+        case NCD_TOKEN_STRING:
+            printf("string %s\n", value);
+            free(value);
+            break;
+        case NCD_TOKEN_ARROW:
+            printf("arrow\n");
+            break;
+        case NCD_TOKEN_TEMPLATE:
+            printf("template\n");
+            break;
+        case NCD_TOKEN_COLON:
+            printf("colon\n");
+            break;
+        case NCD_TOKEN_BRACKET_OPEN:
+            printf("bracket open\n");
+            break;
+        case NCD_TOKEN_BRACKET_CLOSE:
+            printf("bracket close\n");
+            break;
+        case NCD_TOKEN_IF:
+            printf("if\n");
+            break;
+        case NCD_TOKEN_ELIF:
+            printf("elif\n");
+            break;
+        case NCD_TOKEN_ELSE:
+            printf("else\n");
+            break;
+        case NCD_TOKEN_FOREACH:
+            printf("foreach\n");
+            break;
+        case NCD_TOKEN_AS:
+            printf("as\n");
+            break;
+        case NCD_TOKEN_INCLUDE:
+            printf("include\n");
+            break;
+        case NCD_TOKEN_INCLUDE_GUARD:
+            printf("include_guard\n");
+            break;
+        default:
+            ASSERT(0);
+    }
+    
+    return 1;
+}
+
+int main (int argc, char **argv)
+{
+    if (argc < 1) {
+        return 1;
+    }
+    
+    if (argc != 2) {
+        printf("Usage: %s <string>\n", argv[0]);
+        return 1;
+    }
+    
+    BLog_InitStdout();
+    
+    error = 0;
+    
+    NCDConfigTokenizer_Tokenize(argv[1], strlen(argv[1]), tokenizer_output, NULL);
+    
+    if (error) {
+        return 1;
+    }
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/ncd_value_parser_test.c b/external/badvpn_dns/examples/ncd_value_parser_test.c
new file mode 100644
index 0000000..cf1915d
--- /dev/null
+++ b/external/badvpn_dns/examples/ncd_value_parser_test.c
@@ -0,0 +1,78 @@
+/**
+ * @file ncd_value_parser_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <misc/debug.h>
+#include <base/BLog.h>
+#include <ncd/NCDValParser.h>
+#include <ncd/NCDValGenerator.h>
+
+int main (int argc, char *argv[])
+{
+    int res = 1;
+    
+    if (argc != 2) {
+        printf("Usage: %s <string>\n", (argc > 0 ? argv[0] : ""));
+        goto fail0;
+    }
+    
+    BLog_InitStdout();
+    
+    NCDValMem mem;
+    NCDValMem_Init(&mem);
+    
+    // parse
+    NCDValRef val;
+    if (!NCDValParser_Parse(argv[1], strlen(argv[1]), &mem, &val)) {
+        DEBUG("NCDValParser_Parse failed");
+        goto fail1;
+    }
+    
+    // generate value string
+    char *str = NCDValGenerator_Generate(val);
+    if (!str) {
+        DEBUG("NCDValGenerator_Generate failed");
+        goto fail1;
+    }
+    
+    // print value string
+    printf("%s\n", str);
+    
+    res = 0;
+    
+    free(str);
+fail1:
+    NCDValMem_Free(&mem);
+    BLog_Free();
+fail0:
+    return res;
+}
diff --git a/external/badvpn_dns/examples/ncdinterfacemonitor_test.c b/external/badvpn_dns/examples/ncdinterfacemonitor_test.c
new file mode 100644
index 0000000..167f1bd
--- /dev/null
+++ b/external/badvpn_dns/examples/ncdinterfacemonitor_test.c
@@ -0,0 +1,150 @@
+/**
+ * @file ncdinterfacemonitor_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <misc/get_iface_info.h>
+#include <misc/ipaddr6.h>
+#include <misc/debug.h>
+#include <base/BLog.h>
+#include <system/BTime.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <ncd/extra/NCDInterfaceMonitor.h>
+
+BReactor reactor;
+NCDInterfaceMonitor monitor;
+
+static void signal_handler (void *user);
+static void monitor_handler (void *unused, struct NCDInterfaceMonitor_event event);
+static void monitor_handler_error (void *unused);
+
+int main (int argc, char **argv)
+{
+    int ret = 1;
+    
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s <interface>\n", (argc > 0 ? argv[0] : ""));
+        goto fail0;
+    }
+    
+    int ifindex;
+    if (!badvpn_get_iface_info(argv[1], NULL, NULL, &ifindex)) {
+        DEBUG("get_iface_info failed");
+        goto fail0;
+    }
+    
+    BTime_Init();
+    
+    BLog_InitStdout();
+    
+    if (!BNetwork_GlobalInit()) {
+        DEBUG("BNetwork_GlobalInit failed");
+        goto fail1;
+    }
+    
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        goto fail1;
+    }
+    
+    if (!BSignal_Init(&reactor, signal_handler, NULL)) {
+        DEBUG("BSignal_Init failed");
+        goto fail2;
+    }
+    
+    int watch_flags = NCDIFMONITOR_WATCH_LINK|NCDIFMONITOR_WATCH_IPV4_ADDR|NCDIFMONITOR_WATCH_IPV6_ADDR;
+    
+    if (!NCDInterfaceMonitor_Init(&monitor, ifindex, watch_flags, &reactor, NULL, monitor_handler, monitor_handler_error)) {
+        DEBUG("NCDInterfaceMonitor_Init failed");
+        goto fail3;
+    }
+    
+    ret = BReactor_Exec(&reactor);
+    
+    NCDInterfaceMonitor_Free(&monitor);
+fail3:
+    BSignal_Finish();
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return ret;
+}
+
+void signal_handler (void *user)
+{
+    DEBUG("termination requested");
+    
+    BReactor_Quit(&reactor, 1);
+}
+
+void monitor_handler (void *unused, struct NCDInterfaceMonitor_event event)
+{
+    switch (event.event) {
+        case NCDIFMONITOR_EVENT_LINK_UP:
+        case NCDIFMONITOR_EVENT_LINK_DOWN: {
+            const char *type = (event.event == NCDIFMONITOR_EVENT_LINK_UP) ? "up" : "down";
+            printf("link %s\n", type);
+        } break;
+        
+        case NCDIFMONITOR_EVENT_IPV4_ADDR_ADDED:
+        case NCDIFMONITOR_EVENT_IPV4_ADDR_REMOVED: {
+            const char *type = (event.event == NCDIFMONITOR_EVENT_IPV4_ADDR_ADDED) ? "added" : "removed";
+            uint8_t *addr = (uint8_t *)&event.u.ipv4_addr.addr;
+            printf("ipv4 addr %s %d.%d.%d.%d/%d\n", type, (int)addr[0], (int)addr[1], (int)addr[2], (int)addr[3], event.u.ipv4_addr.addr.prefix);
+        } break;
+        
+        case NCDIFMONITOR_EVENT_IPV6_ADDR_ADDED:
+        case NCDIFMONITOR_EVENT_IPV6_ADDR_REMOVED: {
+            const char *type = (event.event == NCDIFMONITOR_EVENT_IPV6_ADDR_ADDED) ? "added" : "removed";
+            
+            char str[IPADDR6_PRINT_MAX];
+            ipaddr6_print_addr(event.u.ipv6_addr.addr.addr, str);
+            
+            int dynamic = !!(event.u.ipv6_addr.addr_flags & NCDIFMONITOR_ADDR_FLAG_DYNAMIC);
+            
+            printf("ipv6 addr %s %s/%d scope=%"PRIu8" dynamic=%d\n", type, str, event.u.ipv6_addr.addr.prefix, event.u.ipv6_addr.scope, dynamic);
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+void monitor_handler_error (void *unused)
+{
+    DEBUG("monitor error");
+    
+    BReactor_Quit(&reactor, 1);
+}
diff --git a/external/badvpn_dns/examples/ncdudevmanager_test.c b/external/badvpn_dns/examples/ncdudevmanager_test.c
new file mode 100644
index 0000000..9bbb3fe
--- /dev/null
+++ b/external/badvpn_dns/examples/ncdudevmanager_test.c
@@ -0,0 +1,161 @@
+/**
+ * @file ncdudevmanager_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <system/BTime.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BUnixSignal.h>
+#include <system/BProcess.h>
+#include <system/BNetwork.h>
+#include <udevmonitor/NCDUdevManager.h>
+
+BReactor reactor;
+BUnixSignal usignal;
+BProcessManager manager;
+NCDUdevManager umanager;
+NCDUdevClient client;
+
+static void signal_handler (void *user, int signo);
+static void client_handler (void *unused, char *devpath, int have_map, BStringMap map);
+
+int main (int argc, char **argv)
+{
+    if (!(argc == 1 || (argc == 2 && !strcmp(argv[1], "--no-udev")))) {
+        fprintf(stderr, "Usage: %s [--no-udev]\n", (argc > 0 ? argv[0] : NULL));
+        goto fail0;
+    }
+    
+    int no_udev = (argc == 2);
+    
+    if (!BNetwork_GlobalInit()) {
+        DEBUG("BNetwork_GlobalInit failed");
+        goto fail0;
+    }
+    
+    BTime_Init();
+    
+    BLog_InitStdout();
+    
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        goto fail1;
+    }
+    
+    sigset_t set;
+    sigemptyset(&set);
+    sigaddset(&set, SIGINT);
+    sigaddset(&set, SIGTERM);
+    sigaddset(&set, SIGHUP);
+    if (!BUnixSignal_Init(&usignal, &reactor, set, signal_handler, NULL)) {
+        fprintf(stderr, "BUnixSignal_Init failed\n");
+        goto fail2;
+    }
+    
+    if (!BProcessManager_Init(&manager, &reactor)) {
+        DEBUG("BProcessManager_Init failed");
+        goto fail3;
+    }
+    
+    NCDUdevManager_Init(&umanager, no_udev, &reactor, &manager);
+    
+    NCDUdevClient_Init(&client, &umanager, NULL, client_handler);
+    
+    BReactor_Exec(&reactor);
+    
+    NCDUdevClient_Free(&client);
+    
+    NCDUdevManager_Free(&umanager);
+    
+    BProcessManager_Free(&manager);
+fail3:
+    BUnixSignal_Free(&usignal, 0);
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return 1;
+}
+
+static void signal_handler (void *user, int signo)
+{
+    if (signo == SIGHUP) {
+        fprintf(stderr, "received SIGHUP, restarting client\n");
+        
+        NCDUdevClient_Free(&client);
+        NCDUdevClient_Init(&client, &umanager, NULL, client_handler);
+    } else {
+        fprintf(stderr, "received %s, exiting\n", (signo == SIGINT ? "SIGINT" : "SIGTERM"));
+        
+        // exit event loop
+        BReactor_Quit(&reactor, 1);
+    }
+}
+
+void client_handler (void *unused, char *devpath, int have_map, BStringMap map)
+{
+    printf("event %s\n", devpath);
+    
+    if (!have_map) {
+        printf("  no map\n");
+    } else {
+        printf("  map:\n");
+        
+        const char *name = BStringMap_First(&map);
+        while (name) {
+            printf("    %s=%s\n", name, BStringMap_Get(&map, name));
+            name = BStringMap_Next(&map, name);
+        }
+    }
+    
+    const BStringMap *cache_map = NCDUdevManager_Query(&umanager, devpath);
+    if (!cache_map) {
+        printf("  no cache\n");
+    } else {
+        printf("  cache:\n");
+        
+        const char *name = BStringMap_First(cache_map);
+        while (name) {
+            printf("    %s=%s\n", name, BStringMap_Get(cache_map, name));
+            name = BStringMap_Next(cache_map, name);
+        }
+    }
+    
+    if (have_map) {
+        BStringMap_Free(&map);
+    }
+    free(devpath);
+}
diff --git a/external/badvpn_dns/examples/ncdudevmonitor_test.c b/external/badvpn_dns/examples/ncdudevmonitor_test.c
new file mode 100644
index 0000000..94b4f6f
--- /dev/null
+++ b/external/badvpn_dns/examples/ncdudevmonitor_test.c
@@ -0,0 +1,152 @@
+/**
+ * @file ncdudevmonitor_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include <system/BTime.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <system/BProcess.h>
+#include <system/BNetwork.h>
+#include <udevmonitor/NCDUdevMonitor.h>
+
+BReactor reactor;
+BProcessManager manager;
+NCDUdevMonitor monitor;
+
+static void signal_handler (void *user);
+static void monitor_handler_event (void *unused);
+static void monitor_handler_error (void *unused, int is_error);
+
+int main (int argc, char **argv)
+{
+    int ret = 1;
+    
+    if (argc < 2 || (strcmp(argv[1], "monitor_udev") && strcmp(argv[1], "monitor_kernel") && strcmp(argv[1], "info"))) {
+        fprintf(stderr, "Usage: %s <monitor_udev/monitor_kernel/info>\n", (argc > 0 ? argv[0] : NULL));
+        goto fail0;
+    }
+    
+    int mode;
+    if (!strcmp(argv[1], "monitor_udev")) {
+        mode = NCDUDEVMONITOR_MODE_MONITOR_UDEV;
+    } else if (!strcmp(argv[1], "monitor_kernel")) {
+        mode = NCDUDEVMONITOR_MODE_MONITOR_KERNEL;
+    } else {
+        mode = NCDUDEVMONITOR_MODE_INFO;
+    }
+    
+    if (!BNetwork_GlobalInit()) {
+        DEBUG("BNetwork_GlobalInit failed");
+        goto fail0;
+    }
+    
+    BTime_Init();
+    
+    BLog_InitStdout();
+    
+    if (!BReactor_Init(&reactor)) {
+        DEBUG("BReactor_Init failed");
+        goto fail1;
+    }
+    
+    if (!BSignal_Init(&reactor, signal_handler, NULL)) {
+        DEBUG("BSignal_Init failed");
+        goto fail2;
+    }
+    
+    if (!BProcessManager_Init(&manager, &reactor)) {
+        DEBUG("BProcessManager_Init failed");
+        goto fail3;
+    }
+    
+    if (!NCDUdevMonitor_Init(&monitor, &reactor, &manager, mode, NULL,
+        monitor_handler_event,
+        monitor_handler_error
+    )) {
+        DEBUG("NCDUdevMonitor_Init failed");
+        goto fail4;
+    }
+    
+    ret = BReactor_Exec(&reactor);
+    
+    NCDUdevMonitor_Free(&monitor);
+fail4:
+    BProcessManager_Free(&manager);
+fail3:
+    BSignal_Finish();
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return ret;
+}
+
+void signal_handler (void *user)
+{
+    DEBUG("termination requested");
+    
+    BReactor_Quit(&reactor, 1);
+}
+
+void monitor_handler_event (void *unused)
+{
+    // accept event
+    NCDUdevMonitor_Done(&monitor);
+    
+    if (NCDUdevMonitor_IsReadyEvent(&monitor)) {
+        printf("ready\n");
+        return;
+    }
+    
+    printf("event\n");
+    
+    int num_props = NCDUdevMonitor_GetNumProperties(&monitor);
+    for (int i = 0; i < num_props; i++) {
+        const char *name;
+        const char *value;
+        NCDUdevMonitor_GetProperty(&monitor, i, &name, &value);
+        printf("  %s=%s\n", name, value);
+    }
+}
+
+void monitor_handler_error (void *unused, int is_error)
+{
+    if (is_error) {
+        DEBUG("monitor error");
+    } else {
+        DEBUG("monitor finished");
+    }
+    
+    BReactor_Quit(&reactor, (is_error ? 1 : 0));
+}
diff --git a/external/badvpn_dns/examples/ncdval_test.c b/external/badvpn_dns/examples/ncdval_test.c
new file mode 100644
index 0000000..6933ed0
--- /dev/null
+++ b/external/badvpn_dns/examples/ncdval_test.c
@@ -0,0 +1,380 @@
+/**
+ * @file ncdval_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include <ncd/NCDVal.h>
+#include <ncd/NCDStringIndex.h>
+#include <ncd/static_strings.h>
+#include <base/BLog.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <misc/offset.h>
+
+#define FORCE(cmd) if (!(cmd)) { fprintf(stderr, "failed\n"); exit(1); }
+
+struct composed_string {
+    BRefTarget ref_target;
+    size_t length;
+    size_t chunk_size;
+    char **chunks;
+};
+
+static void composed_string_ref_target_func_release (BRefTarget *ref_target)
+{
+    struct composed_string *cs = UPPER_OBJECT(ref_target, struct composed_string, ref_target);
+    
+    size_t num_chunks = cs->length / cs->chunk_size;
+    if (cs->length % cs->chunk_size) {
+        num_chunks++;
+    }
+    
+    for (size_t i = 0; i < num_chunks; i++) {
+        BFree(cs->chunks[i]);
+    }
+    
+    BFree(cs->chunks);
+    BFree(cs);
+}
+
+static void composed_string_func_getptr (void *user, size_t offset, const char **out_data, size_t *out_length)
+{
+    struct composed_string *cs = user;
+    ASSERT(offset < cs->length)
+    
+    *out_data = cs->chunks[offset / cs->chunk_size] + (offset % cs->chunk_size);
+    *out_length = cs->chunk_size - (offset % cs->chunk_size);
+}
+
+static NCDValRef build_composed_string (NCDValMem *mem, const char *data, size_t length, size_t chunk_size)
+{
+    ASSERT(chunk_size > 0)
+    
+    struct composed_string *cs = BAlloc(sizeof(*cs));
+    if (!cs) {
+        goto fail0;
+    }
+    
+    cs->length = length;
+    cs->chunk_size = chunk_size;
+    
+    size_t num_chunks = cs->length / cs->chunk_size;
+    if (cs->length % cs->chunk_size) {
+        num_chunks++;
+    }
+    
+    cs->chunks = BAllocArray(num_chunks, sizeof(cs->chunks[0]));
+    if (!cs->chunk_size) {
+        goto fail1;
+    }
+    
+    size_t i;
+    for (i = 0; i < num_chunks; i++) {
+        cs->chunks[i] = BAlloc(cs->chunk_size);
+        if (!cs->chunks[i]) {
+            goto fail2;
+        }
+        
+        size_t to_copy = length;
+        if (to_copy > cs->chunk_size) {
+            to_copy = cs->chunk_size;
+        }
+        
+        memcpy(cs->chunks[i], data, to_copy);
+        data += to_copy;
+        length -= to_copy;
+    }
+    
+    BRefTarget_Init(&cs->ref_target, composed_string_ref_target_func_release);
+    
+    NCDValComposedStringResource resource;
+    resource.func_getptr = composed_string_func_getptr;
+    resource.user = cs;
+    resource.ref_target = &cs->ref_target;
+    
+    NCDValRef val = NCDVal_NewComposedString(mem, resource, 0, cs->length);
+    BRefTarget_Deref(&cs->ref_target);
+    return val;
+    
+fail2:
+    while (i-- > 0) {
+        BFree(cs->chunks[i]);
+    }
+    BFree(cs->chunks);
+fail1:
+    BFree(cs);
+fail0:
+    return NCDVal_NewInvalid();
+}
+
+static void test_string (NCDValRef str, const char *data, size_t length)
+{
+    FORCE( !NCDVal_IsInvalid(str) )
+    FORCE( NCDVal_IsString(str) )
+    FORCE( NCDVal_StringLength(str) == length )
+    FORCE( NCDVal_StringHasNulls(str) == !!memchr(data, '\0', length) )
+    FORCE( NCDVal_IsStringNoNulls(str) == !memchr(data, '\0', length) )
+    FORCE( NCDVal_StringRegionEquals(str, 0, length, data) )
+    
+    b_cstring cstr = NCDVal_StringCstring(str);
+    
+    for (size_t i = 0; i < length; i++) {
+        size_t chunk_length;
+        const char *chunk_data = b_cstring_get(cstr, i, length - i, &chunk_length);
+        
+        FORCE( chunk_length > 0 )
+        FORCE( chunk_length <= length - i )
+        FORCE( !memcmp(chunk_data, data + i, chunk_length) )
+        FORCE( NCDVal_StringRegionEquals(str, i, chunk_length, data + i) )
+        FORCE( b_cstring_memcmp(cstr, b_cstring_make_buf(data, length), i, i, chunk_length) == 0 )
+        FORCE( b_cstring_memcmp(cstr, b_cstring_make_buf(data + i, length - i), i, 0, chunk_length) == 0 )
+    }
+}
+
+static void print_indent (int indent)
+{
+    for (int i = 0; i < indent; i++) {
+        printf("  ");
+    }
+}
+
+static void print_value (NCDValRef val, unsigned int indent)
+{
+    switch (NCDVal_Type(val)) {
+        case NCDVAL_STRING: {
+            NCDValNullTermString nts;
+            FORCE( NCDVal_StringNullTerminate(val, &nts) )
+            
+            print_indent(indent);
+            printf("string(%zu) %s\n", NCDVal_StringLength(val), nts.data);
+            
+            NCDValNullTermString_Free(&nts);
+        } break;
+        
+        case NCDVAL_LIST: {
+            size_t count = NCDVal_ListCount(val);
+            
+            print_indent(indent);
+            printf("list(%zu)\n", NCDVal_ListCount(val));
+            
+            for (size_t i = 0; i < count; i++) {
+                NCDValRef elem_val = NCDVal_ListGet(val, i);
+                print_value(elem_val, indent + 1);
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            print_indent(indent);
+            printf("map(%zu)\n", NCDVal_MapCount(val));
+            
+            for (NCDValMapElem e = NCDVal_MapOrderedFirst(val); !NCDVal_MapElemInvalid(e); e = NCDVal_MapOrderedNext(val, e)) {
+                NCDValRef ekey = NCDVal_MapElemKey(val, e);
+                NCDValRef eval = NCDVal_MapElemVal(val, e);
+                
+                print_indent(indent + 1);
+                printf("key=\n");
+                print_value(ekey, indent + 2);
+                
+                print_indent(indent + 1);
+                printf("val=\n");
+                print_value(eval, indent + 2);
+            }
+        } break;
+    }
+}
+
+int main ()
+{
+    int res;
+    
+    BLog_InitStdout();
+    
+    NCDStringIndex string_index;
+    FORCE( NCDStringIndex_Init(&string_index) )
+    
+    // Some basic usage of values.
+    
+    NCDValMem mem;
+    NCDValMem_Init(&mem);
+    
+    NCDValRef s1 = NCDVal_NewString(&mem, "Hello World");
+    test_string(s1, "Hello World", 11);
+    ASSERT( NCDVal_IsString(s1) )
+    ASSERT( !NCDVal_IsIdString(s1) )
+    ASSERT( NCDVal_Type(s1) == NCDVAL_STRING )
+    
+    NCDValRef s2 = NCDVal_NewString(&mem, "This is reeeeeeeeeeeeeallllllllyyyyy fun!");
+    FORCE( !NCDVal_IsInvalid(s2) )
+    
+    NCDValRef l1 = NCDVal_NewList(&mem, 10);
+    FORCE( !NCDVal_IsInvalid(l1) )
+    
+    FORCE( NCDVal_ListAppend(l1, s1) )
+    FORCE( NCDVal_ListAppend(l1, s2) )
+    
+    print_value(s1, 0);
+    print_value(s2, 0);
+    print_value(l1, 0);
+    
+    NCDValRef k1 = NCDVal_NewString(&mem, "K1");
+    FORCE( !NCDVal_IsInvalid(k1) )
+    NCDValRef v1 = NCDVal_NewString(&mem, "V1");
+    FORCE( !NCDVal_IsInvalid(v1) )
+    
+    NCDValRef k2 = NCDVal_NewString(&mem, "K2");
+    FORCE( !NCDVal_IsInvalid(k2) )
+    NCDValRef v2 = NCDVal_NewString(&mem, "V2");
+    FORCE( !NCDVal_IsInvalid(v2) )
+    
+    NCDValRef m1 = NCDVal_NewMap(&mem, 3);
+    FORCE( !NCDVal_IsInvalid(m1) )
+    
+    FORCE( NCDVal_MapInsert(m1, k1, v1, &res) && res )
+    FORCE( NCDVal_MapInsert(m1, k2, v2, &res) && res )
+    
+    ASSERT( NCDVal_MapGetValue(m1, "K1").idx == v1.idx )
+    ASSERT( NCDVal_MapGetValue(m1, "K2").idx == v2.idx )
+    ASSERT( NCDVal_IsInvalid(NCDVal_MapGetValue(m1, "K3")) )
+    
+    NCDValRef ids1 = NCDVal_NewIdString(&mem, NCD_STRING_ARG1, &string_index);
+    test_string(ids1, "_arg1", 5);
+    ASSERT( !memcmp(NCDVal_StringData(ids1), "_arg1", 5) )
+    ASSERT( NCDVal_StringLength(ids1) == 5 )
+    ASSERT( !NCDVal_StringHasNulls(ids1) )
+    ASSERT( NCDVal_StringEquals(ids1, "_arg1") )
+    ASSERT( NCDVal_Type(ids1) == NCDVAL_STRING )
+    ASSERT( NCDVal_IsIdString(ids1) )
+    
+    NCDValRef ids2 = NCDVal_NewIdString(&mem, NCD_STRING_ARG2, &string_index);
+    test_string(ids2, "_arg2", 5);
+    ASSERT( !memcmp(NCDVal_StringData(ids2), "_arg2", 5) )
+    ASSERT( NCDVal_StringLength(ids2) == 5 )
+    ASSERT( !NCDVal_StringHasNulls(ids2) )
+    ASSERT( NCDVal_StringEquals(ids2, "_arg2") )
+    ASSERT( NCDVal_Type(ids2) == NCDVAL_STRING )
+    ASSERT( NCDVal_IsIdString(ids2) )
+    
+    FORCE( NCDVal_MapInsert(m1, ids1, ids2, &res) && res )
+    
+    ASSERT( NCDVal_MapGetValue(m1, "_arg1").idx == ids2.idx )
+    
+    print_value(m1, 0);
+    
+    NCDValRef copy = NCDVal_NewCopy(&mem, m1);
+    FORCE( !NCDVal_IsInvalid(copy) )
+    ASSERT( NCDVal_Compare(copy, m1) == 0 )
+    
+    NCDValMem_Free(&mem);
+    
+    // Try to make copies of a string within the same memory object.
+    // This is an evil test because we cannot simply copy a string using e.g.
+    // NCDVal_NewStringBin() - it requires that the buffer passed
+    // be outside the memory object of the new string.
+    // We use NCDVal_NewCopy(), which takes care of this by creating
+    // an uninitialized string using NCDVal_NewStringUninitialized() and
+    // then copyng the data.
+    
+    NCDValMem_Init(&mem);
+    
+    NCDValRef s[100];
+    
+    s[0] = NCDVal_NewString(&mem, "Eeeeeeeeeeeevil.");
+    FORCE( !NCDVal_IsInvalid(s[0]) )
+    
+    for (int i = 1; i < 100; i++) {
+        s[i] = NCDVal_NewCopy(&mem, s[i - 1]);
+        FORCE( !NCDVal_IsInvalid(s[i]) )
+        ASSERT( NCDVal_StringEquals(s[i - 1], "Eeeeeeeeeeeevil.") )
+        ASSERT( NCDVal_StringEquals(s[i], "Eeeeeeeeeeeevil.") )
+    }
+    
+    for (int i = 0; i < 100; i++) {
+        ASSERT( NCDVal_StringEquals(s[i], "Eeeeeeeeeeeevil.") )
+    }
+    
+    NCDValMem_Free(&mem);
+    
+    NCDValMem_Init(&mem);
+    
+    NCDValRef cstr1 = build_composed_string(&mem, "Hello World", 11, 3);
+    test_string(cstr1, "Hello World", 11);
+    FORCE( NCDVal_IsComposedString(cstr1) )
+    FORCE( !NCDVal_IsContinuousString(cstr1) )
+    FORCE( NCDVal_StringEquals(cstr1, "Hello World") )
+    FORCE( !NCDVal_StringEquals(cstr1, "Hello World ") )
+    FORCE( !NCDVal_StringEquals(cstr1, "Hello WorlD") )
+    
+    NCDValRef cstr2 = build_composed_string(&mem, "GoodBye", 7, 1);
+    test_string(cstr2, "GoodBye", 7);
+    FORCE( NCDVal_IsComposedString(cstr2) )
+    FORCE( !NCDVal_IsContinuousString(cstr2) )
+    FORCE( NCDVal_StringEquals(cstr2, "GoodBye") )
+    FORCE( !NCDVal_StringEquals(cstr2, " GoodBye") )
+    FORCE( !NCDVal_StringEquals(cstr2, "goodBye") )
+    
+    NCDValRef cstr3 = build_composed_string(&mem, "Bad\x00String", 10, 4);
+    test_string(cstr3, "Bad\x00String", 10);
+    FORCE( NCDVal_IsComposedString(cstr3) )
+    FORCE( !NCDVal_IsContinuousString(cstr3) )
+    
+    FORCE( NCDVal_StringMemCmp(cstr1, cstr2, 1, 2, 3) < 0 )
+    FORCE( NCDVal_StringMemCmp(cstr1, cstr2, 7, 1, 4) > 0 )
+    
+    char buf[10];
+    NCDVal_StringCopyOut(cstr1, 1, 10, buf);
+    FORCE( !memcmp(buf, "ello World", 10) )
+    
+    NCDValRef clist1 = NCDVal_NewList(&mem, 3);
+    FORCE( !NCDVal_IsInvalid(clist1) )
+    FORCE( NCDVal_ListAppend(clist1, cstr1) )
+    FORCE( NCDVal_ListAppend(clist1, cstr2) )
+    FORCE( NCDVal_ListAppend(clist1, cstr3) )
+    FORCE( NCDVal_ListCount(clist1) == 3 )
+    
+    FORCE( NCDValMem_ConvertNonContinuousStrings(&mem, &clist1) )
+    FORCE( NCDVal_ListCount(clist1) == 3 )
+    
+    NCDValRef fixed_str1 = NCDVal_ListGet(clist1, 0);
+    NCDValRef fixed_str2 = NCDVal_ListGet(clist1, 1);
+    NCDValRef fixed_str3 = NCDVal_ListGet(clist1, 2);
+    
+    FORCE( NCDVal_IsContinuousString(fixed_str1) )
+    FORCE( NCDVal_IsContinuousString(fixed_str2) )
+    FORCE( NCDVal_IsContinuousString(fixed_str3) )
+    
+    test_string(fixed_str1, "Hello World", 11);
+    test_string(fixed_str2, "GoodBye", 7);
+    test_string(fixed_str3, "Bad\x00String", 10);
+    
+    NCDValMem_Free(&mem);
+    
+    NCDStringIndex_Free(&string_index);
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/ncdvalcons_test.c b/external/badvpn_dns/examples/ncdvalcons_test.c
new file mode 100644
index 0000000..7a876ed
--- /dev/null
+++ b/external/badvpn_dns/examples/ncdvalcons_test.c
@@ -0,0 +1,111 @@
+/**
+ * @file ncdvalcons_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <misc/debug.h>
+#include <ncd/NCDValCons.h>
+#include <ncd/NCDValGenerator.h>
+
+static NCDValMem mem;
+static NCDValCons cons;
+
+static NCDValConsVal make_string (const char *data)
+{
+    NCDValConsVal val;
+    int error;
+    int res = NCDValCons_NewString(&cons, (const uint8_t *)data, strlen(data), &val, &error);
+    ASSERT_FORCE(res)
+    return val;
+}
+
+static NCDValConsVal make_list (void)
+{
+    NCDValConsVal val;
+    NCDValCons_NewList(&cons, &val);
+    return val;
+}
+
+static NCDValConsVal make_map (void)
+{
+    NCDValConsVal val;
+    NCDValCons_NewMap(&cons, &val);
+    return val;
+}
+
+static NCDValConsVal list_prepend (NCDValConsVal list, NCDValConsVal elem)
+{
+    int error;
+    int res = NCDValCons_ListPrepend(&cons, &list, elem, &error);
+    ASSERT_FORCE(res)
+    return list;
+}
+
+static NCDValConsVal map_insert (NCDValConsVal map, NCDValConsVal key, NCDValConsVal value)
+{
+    int error;
+    int res = NCDValCons_MapInsert(&cons, &map, key, value, &error);
+    ASSERT_FORCE(res)
+    return map;
+}
+
+static NCDValRef complete (NCDValConsVal cval)
+{
+    int error;
+    NCDValRef val;
+    int res = NCDValCons_Complete(&cons, cval, &val, &error);
+    ASSERT_FORCE(res)
+    return val;
+}
+
+int main ()
+{
+    NCDValMem_Init(&mem);
+    
+    int res = NCDValCons_Init(&cons, &mem);
+    ASSERT_FORCE(res)
+    
+    NCDValRef val1 = complete(list_prepend(list_prepend(list_prepend(make_list(), make_string("hello")), make_string("world")), make_list()));
+    char *str1 = NCDValGenerator_Generate(val1);
+    ASSERT_FORCE(str1)
+    ASSERT_FORCE(!strcmp(str1, "{{}, \"world\", \"hello\"}"))
+    free(str1);
+    
+    NCDValRef val2 = complete(map_insert(map_insert(map_insert(make_map(), make_list(), make_list()), make_string("A"), make_list()), make_string("B"), make_list()));
+    char *str2 = NCDValGenerator_Generate(val2);
+    ASSERT_FORCE(str2)
+    printf("%s\n", str2);
+    ASSERT_FORCE(!strcmp(str2, "[\"A\":{}, \"B\":{}, {}:{}]"))
+    free(str2);
+    
+    NCDValCons_Free(&cons);
+    NCDValMem_Free(&mem);
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/parse_number_test.c b/external/badvpn_dns/examples/parse_number_test.c
new file mode 100644
index 0000000..d393c3f
--- /dev/null
+++ b/external/badvpn_dns/examples/parse_number_test.c
@@ -0,0 +1,130 @@
+/**
+ * @file parse_number_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+
+#include <misc/parse_number.h>
+#include <misc/debug.h>
+
+static void test_random (int num_digits, int digit_modulo)
+{
+    ASSERT(num_digits > 0);
+    ASSERT(digit_modulo > 0);
+    
+    uint8_t digits[40];
+    
+    for (int i = 0; i < num_digits; i++) {
+        digits[i] = '0' + (rand() % digit_modulo);
+    }
+    digits[num_digits] = '\0';
+    
+    char *endptr;
+    uintmax_t std_num = strtoumax((const char *)digits, &endptr, 10);
+    int std_res = !*endptr && !(std_num == UINTMAX_MAX && errno == ERANGE);
+    
+    uintmax_t num = 0;
+    int res = parse_unsigned_integer_bin((const char *)digits, num_digits, &num);
+    
+    if (res != std_res) {
+        printf("fail1 %s\n", (const char *)digits);
+        ASSERT_FORCE(0);
+    }
+    
+    if (res && num != std_num) {
+        printf("fail2 %s\n", (const char *)digits);
+        ASSERT_FORCE(0);
+    }
+    
+    if (res) {
+        uint8_t *nozero_digits = digits;
+        while (*nozero_digits == '0' && nozero_digits != &digits[num_digits - 1]) {
+            nozero_digits++;
+        }
+        
+        char buf[40];
+        int size = compute_decimal_repr_size(num);
+        generate_decimal_repr(num, buf, size);
+        buf[size] = '\0';
+        ASSERT_FORCE(!strcmp(buf, (const char *)nozero_digits));
+    }
+}
+
+static void test_value (uintmax_t x)
+{
+    char str[40];
+    sprintf(str, "%" PRIuMAX, x);
+    uintmax_t y;
+    int res = parse_unsigned_integer_bin(str, strlen(str), &y);
+    ASSERT_FORCE(res);
+    ASSERT_FORCE(y == x);
+    
+    char str2[40];
+    int size = compute_decimal_repr_size(x);
+    generate_decimal_repr(x, str2, size);
+    str2[size] = '\0';
+    
+    ASSERT_FORCE(!strcmp(str2, str));
+}
+
+static void test_value_range (uintmax_t start, uintmax_t count)
+{
+    uintmax_t i = start;
+    do {
+        test_value(i);
+        i++;
+    } while (i != start + count);
+}
+
+int main ()
+{
+    srand(time(NULL));
+    
+    for (int num_digits = 1; num_digits <= 22; num_digits++) {
+        for (int i = 0; i < 1000000; i++) {
+            test_random(num_digits, 10);
+        }
+        for (int i = 0; i < 1000000; i++) {
+            test_random(num_digits, 11);
+        }
+    }
+    
+    test_value_range(UINTMAX_C(0), 5000000);
+    test_value_range(UINTMAX_C(100000000), 5000000);
+    test_value_range(UINTMAX_C(258239003), 5000000);
+    test_value_range(UINTMAX_C(8241096180752634), 5000000);
+    test_value_range(UINTMAX_C(9127982390882308083), 5000000);
+    test_value_range(UINTMAX_C(18446744073700000000), 20000000);
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/predicate_test.c b/external/badvpn_dns/examples/predicate_test.c
new file mode 100644
index 0000000..324a960
--- /dev/null
+++ b/external/badvpn_dns/examples/predicate_test.c
@@ -0,0 +1,116 @@
+/**
+ * @file predicate_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <predicate/BPredicate.h>
+#include <base/BLog.h>
+
+static int func_hello (void *user, void **args)
+{
+    return 1;
+}
+
+static int func_neg (void *user, void **args)
+{
+    int arg = *((int *)args[0]);
+    
+    return !arg;
+}
+
+static int func_conj (void *user, void **args)
+{
+    int arg1 = *((int *)args[0]);
+    int arg2 = *((int *)args[1]);
+    
+    return (arg1 && arg2);
+}
+
+static int func_strcmp (void *user, void **args)
+{
+    char *arg1 = (char *)args[0];
+    char *arg2 = (char *)args[1];
+    
+    return (!strcmp(arg1, arg2));
+}
+
+static int func_error (void *user, void **args)
+{
+    return -1;
+}
+
+int main (int argc, char **argv)
+{
+    if (argc != 2) {
+        fprintf(stderr, "Usage: %s <predicate>\n", argv[0]);
+        return 1;
+    }
+    
+    // init logger
+    BLog_InitStdout();
+    
+    // init predicate
+    BPredicate pr;
+    if (!BPredicate_Init(&pr, argv[1])) {
+        fprintf(stderr, "BPredicate_Init failed\n");
+        return 1;
+    }
+    
+    // init functions
+    BPredicateFunction f_hello;
+    BPredicateFunction_Init(&f_hello, &pr, "hello", NULL, 0, func_hello, NULL);
+    int arr1[] = {PREDICATE_TYPE_BOOL};
+    BPredicateFunction f_neg;
+    BPredicateFunction_Init(&f_neg, &pr, "neg", arr1, 1, func_neg, NULL);
+    int arr2[] = {PREDICATE_TYPE_BOOL, PREDICATE_TYPE_BOOL};
+    BPredicateFunction f_conj;
+    BPredicateFunction_Init(&f_conj, &pr, "conj", arr2, 2, func_conj, NULL);
+    int arr3[] = {PREDICATE_TYPE_STRING, PREDICATE_TYPE_STRING};
+    BPredicateFunction f_strcmp;
+    BPredicateFunction_Init(&f_strcmp, &pr, "strcmp", arr3, 2, func_strcmp, NULL);
+    BPredicateFunction f_error;
+    BPredicateFunction_Init(&f_error, &pr, "error", NULL, 0, func_error, NULL);
+    
+    // evaluate
+    int result = BPredicate_Eval(&pr);
+    printf("%d\n", result);
+    
+    // free functions
+    BPredicateFunction_Free(&f_hello);
+    BPredicateFunction_Free(&f_neg);
+    BPredicateFunction_Free(&f_conj);
+    BPredicateFunction_Free(&f_strcmp);
+    BPredicateFunction_Free(&f_error);
+    
+    // free predicate
+    BPredicate_Free(&pr);
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/savl_test.c b/external/badvpn_dns/examples/savl_test.c
new file mode 100644
index 0000000..18cf191
--- /dev/null
+++ b/external/badvpn_dns/examples/savl_test.c
@@ -0,0 +1,135 @@
+/**
+ * @file savl_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <misc/compare.h>
+#include <structure/SAvl.h>
+#include <security/BRandom.h>
+
+struct mynode;
+
+#include "savl_test_tree.h"
+#include <structure/SAvl_decl.h>
+
+struct mynode {
+    int used;
+    int num;
+    MyTreeNode tree_node;
+};
+
+#include "savl_test_tree.h"
+#include <structure/SAvl_impl.h>
+
+static void verify (MyTree *tree)
+{
+    printf("Verifying...\n");
+    MyTree_Verify(tree, 0);
+}
+
+int main (int argc, char **argv)
+{
+    int num_nodes;
+    int num_random_delete;
+    
+    if (argc != 3 || (num_nodes = atoi(argv[1])) <= 0 || (num_random_delete = atoi(argv[2])) < 0) {
+        fprintf(stderr, "Usage: %s <num> <numrandomdelete>\n", (argc > 0 ? argv[0] : NULL));
+        return 1;
+    }
+    
+    struct mynode *nodes = (struct mynode *)BAllocArray(num_nodes, sizeof(*nodes));
+    ASSERT_FORCE(nodes)
+    
+    int *values_ins = (int *)BAllocArray(num_nodes, sizeof(int));
+    ASSERT_FORCE(values_ins)
+    
+    int *values = (int *)BAllocArray(num_random_delete, sizeof(int));
+    ASSERT_FORCE(values)
+    
+    MyTree tree;
+    MyTree_Init(&tree);
+    verify(&tree);
+    
+    printf("Inserting random values...\n");
+    int inserted = 0;
+    BRandom_randomize((uint8_t *)values_ins, num_nodes * sizeof(int));
+    for (int i = 0; i < num_nodes; i++) {
+        nodes[i].num = values_ins[i];
+        if (MyTree_Insert(&tree, 0, &nodes[i], NULL)) {
+            nodes[i].used = 1;
+            inserted++;
+        } else {
+            nodes[i].used = 0;
+            printf("Insert collision!\n");
+        }
+    }
+    printf("Inserted %d entries\n", inserted);
+    ASSERT_FORCE(MyTree_Count(&tree, 0) == inserted)
+    verify(&tree);
+    
+    printf("Removing random entries...\n");
+    int removed1 = 0;
+    BRandom_randomize((uint8_t *)values, num_random_delete * sizeof(int));
+    for (int i = 0; i < num_random_delete; i++) {
+        int index = (((unsigned int *)values)[i] % num_nodes);
+        struct mynode *node = nodes + index;
+        if (node->used) {
+            MyTree_Remove(&tree, 0, node);
+            node->used = 0;
+            removed1++;
+        }
+    }
+    printf("Removed %d entries\n", removed1);
+    ASSERT_FORCE(MyTree_Count(&tree, 0) == inserted - removed1)
+    verify(&tree);
+    
+    printf("Removing remaining...\n");
+    int removed2 = 0;
+    while (!MyTree_IsEmpty(&tree)) {
+        struct mynode *node = MyTree_GetFirst(&tree, 0);
+        ASSERT_FORCE(node->used)
+        MyTree_Remove(&tree, 0, node);
+        node->used = 0;
+        removed2++;
+    }
+    printf("Removed %d entries\n", removed2);
+    ASSERT_FORCE(MyTree_IsEmpty(&tree))
+    ASSERT_FORCE(removed1 + removed2 == inserted)
+    ASSERT_FORCE(MyTree_Count(&tree, 0) == 0)
+    verify(&tree);
+    
+    BFree(nodes);
+    BFree(values_ins);
+    BFree(values);
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/examples/savl_test_tree.h b/external/badvpn_dns/examples/savl_test_tree.h
new file mode 100644
index 0000000..41964e9
--- /dev/null
+++ b/external/badvpn_dns/examples/savl_test_tree.h
@@ -0,0 +1,9 @@
+#define SAVL_PARAM_NAME MyTree
+#define SAVL_PARAM_FEATURE_COUNTS 1
+#define SAVL_PARAM_FEATURE_NOKEYS 1
+#define SAVL_PARAM_TYPE_ENTRY struct mynode
+#define SAVL_PARAM_TYPE_ARG int
+#define SAVL_PARAM_TYPE_COUNT int
+#define SAVL_PARAM_VALUE_COUNT_MAX INT_MAX
+#define SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) B_COMPARE((entry1)->num, (entry2)->num)
+#define SAVL_PARAM_MEMBER_NODE tree_node
diff --git a/external/badvpn_dns/examples/stdin_input.c b/external/badvpn_dns/examples/stdin_input.c
new file mode 100644
index 0000000..8ff752c
--- /dev/null
+++ b/external/badvpn_dns/examples/stdin_input.c
@@ -0,0 +1,138 @@
+/**
+ * @file stdin_input.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Example program which reads stdin and waits for SIGINT and SIGTERM.
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+
+#include <base/DebugObject.h>
+#include <system/BReactor.h>
+#include <system/BNetwork.h>
+#include <system/BConnection.h>
+#include <system/BUnixSignal.h>
+
+#define BUF_SIZE 64
+
+BReactor reactor;
+BConnection pipe_con;
+BUnixSignal usignal;
+StreamRecvInterface *source_if;
+uint8_t buf[BUF_SIZE + 1];
+
+static void signal_handler (void *user, int signo)
+{
+    fprintf(stderr, "received %s, exiting\n", (signo == SIGINT ? "SIGINT" : "SIGTERM"));
+    
+    // exit event loop
+    BReactor_Quit(&reactor, 1);
+}
+
+static void connection_handler (void *user, int event)
+{
+    if (event == BCONNECTION_EVENT_RECVCLOSED) {
+        fprintf(stderr, "pipe closed\n");
+    } else {
+        fprintf(stderr, "pipe error\n");
+    }
+    
+    // exit event loop
+    BReactor_Quit(&reactor, (event == BCONNECTION_EVENT_RECVCLOSED ? 0 : 1));
+}
+
+static void input_handler_done (void *user, int data_len)
+{
+    // receive next chunk
+    StreamRecvInterface_Receiver_Recv(source_if, buf, BUF_SIZE);
+    
+    // print this chunk
+    buf[data_len] = '\0';
+    printf("Received: '%s'\n", buf);
+}
+
+int main ()
+{
+    int ret = 1;
+    
+    BLog_InitStdout();
+    
+    // init network
+    if (!BNetwork_GlobalInit()) {
+        fprintf(stderr, "BNetwork_GlobalInit failed\n");
+        goto fail1;
+    }
+    
+    // init reactor (event loop)
+    if (!BReactor_Init(&reactor)) {
+        fprintf(stderr, "BReactor_Init failed\n");
+        goto fail1;
+    }
+    
+    // init signal handling
+    sigset_t set;
+    sigemptyset(&set);
+    sigaddset(&set, SIGINT);
+    sigaddset(&set, SIGTERM);
+    if (!BUnixSignal_Init(&usignal, &reactor, set, signal_handler, NULL)) {
+        fprintf(stderr, "BUnixSignal_Init failed\n");
+        goto fail2;
+    }
+    
+    // init BConnection object backed by the stdin fd
+    if (!BConnection_Init(&pipe_con, BConnection_source_pipe(0), &reactor, NULL, connection_handler)) {
+        fprintf(stderr, "BConnection_Init failed\n");
+        goto fail3;
+    }
+    
+    // init connection receive interface
+    BConnection_RecvAsync_Init(&pipe_con);
+    source_if = BConnection_RecvAsync_GetIf(&pipe_con);
+    
+    // init receive done callback
+    StreamRecvInterface_Receiver_Init(source_if, input_handler_done, NULL);
+    
+    // receive first chunk
+    StreamRecvInterface_Receiver_Recv(source_if, buf, BUF_SIZE);
+    
+    // run event loop
+    ret = BReactor_Exec(&reactor);
+    
+    BConnection_RecvAsync_Free(&pipe_con);
+    BConnection_Free(&pipe_con);
+fail3:
+    BUnixSignal_Free(&usignal, 0);
+fail2:
+    BReactor_Free(&reactor);
+fail1:
+    BLog_Free();
+    DebugObjectGlobal_Finish();
+    return ret;
+}
diff --git a/external/badvpn_dns/examples/substring_test.c b/external/badvpn_dns/examples/substring_test.c
new file mode 100644
index 0000000..4a4b6c8
--- /dev/null
+++ b/external/badvpn_dns/examples/substring_test.c
@@ -0,0 +1,204 @@
+/**
+ * @file substring_test.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <misc/substring.h>
+#include <misc/debug.h>
+#include <misc/balloc.h>
+#include <misc/print_macros.h>
+
+static int find_substring_slow (const char *str, size_t str_len, const char *sub, size_t sub_len, size_t *out_pos)
+{
+    ASSERT(sub_len > 0)
+    
+    if (str_len < sub_len) {
+        return 0;
+    }
+    
+    for (size_t i = 0; i <= str_len - sub_len; i++) {
+        if (!memcmp(str + i, sub, sub_len)) {
+            *out_pos = i;
+            return 1;
+        }
+    }
+    
+    return 0;
+}
+
+static void print_data (const char *str, size_t str_len)
+{
+    while (str_len > 0) {
+        printf("%02"PRIx8" ", (uint8_t)(*str));
+        str++;
+        str_len--;
+    }
+    printf("\n");
+}
+
+static void print_table (const size_t *table, size_t len)
+{
+    for (size_t i = 1; i < len; i++) {
+        printf("%zu ", table[i]);
+    }
+    printf("\n");
+}
+
+static void test_tables (int len, int count)
+{
+    ASSERT(len > 0)
+    ASSERT(count >= 0)
+    
+    char *word = (char *)BAllocSize(bsize_fromint(len));
+    ASSERT_FORCE(word)
+    
+    size_t *table = (size_t *)BAllocSize(bsize_mul(bsize_fromint(len), bsize_fromsize(sizeof(table[0]))));
+    ASSERT_FORCE(table)
+    
+    for (int i = 0; i < count; i++) {
+        for (int j = 0; j < len; j++) {
+            word[j] = rand() % 2;
+        }
+        
+        build_substring_backtrack_table(word, len, table);
+        
+        for (int j = 1; j < len; j++) {
+            for (int k = j - 1; k >= 0; k--) {
+                if (!memcmp(word + j - k, word, k)) {
+                    ASSERT_FORCE(table[j] == k)
+                    break;
+                }
+            }
+        }
+    }
+    
+    BFree(table);
+    BFree(word);
+}
+
+static void test_substring (int word_len, int text_len, int word_count, int text_count)
+{
+    assert(word_len > 0);
+    assert(text_len >= 0);
+    assert(word_count >= 0);
+    assert(text_count >= 0);
+    
+    char *word = (char *)BAllocSize(bsize_fromint(word_len));
+    ASSERT_FORCE(word)
+    
+    size_t *table = (size_t *)BAllocSize(bsize_mul(bsize_fromint(word_len), bsize_fromsize(sizeof(table[0]))));
+    ASSERT_FORCE(table)
+    
+    char *text = (char *)BAllocSize(bsize_fromint(text_len));
+    ASSERT_FORCE(text)
+    
+    for (int i = 0; i < word_count; i++) {
+        for (int j = 0; j < word_len; j++) {
+            word[j] = rand() % 2;
+        }
+        
+        build_substring_backtrack_table(word, word_len, table);
+        
+        for (int j = 0; j < text_count; j++) {
+            for (int k = 0; k < text_len; k++) {
+                text[k] = rand() % 2;
+            }
+            
+            size_t pos = 36; // to remove warning
+            int res = find_substring(text, text_len, word, word_len, table, &pos);
+            
+            size_t spos = 59; // to remove warning
+            int sres = find_substring_slow(text, text_len, word, word_len, &spos);
+            
+            ASSERT_FORCE(res == sres)
+            if (res) {
+                ASSERT_FORCE(pos == spos)
+            }
+        }
+    }
+    
+    BFree(text);
+    BFree(table);
+    BFree(word);
+}
+
+int main (int argc, char *argv[])
+{
+    if (argc != 7) {
+        printf("Usage: %s <tables length> <tables count> <word len> <text len> <word count> <text count>\n", (argc == 0 ? "" : argv[0]));
+        return 1;
+    }
+    
+    int tables_len = atoi(argv[1]);
+    int tables_count = atoi(argv[2]);
+    int word_len = atoi(argv[3]);
+    int text_len = atoi(argv[4]);
+    int word_count = atoi(argv[5]);
+    int text_count = atoi(argv[6]);
+    
+    if (tables_len <= 0 || tables_count < 0 || word_len <= 0 || text_len < 0 || word_count < 0 || text_count < 0) {
+        printf("Bad arguments.\n");
+        return 1;
+    }
+    
+    srand(time(NULL));
+    
+    test_tables(tables_len, tables_count);
+    
+    test_substring(word_len, text_len, word_count, text_count);
+    
+    {
+        char text[] = "aggagaa";
+        char word[] = "aga";
+        size_t table[sizeof(word) - 1];
+        build_substring_backtrack_table(word, strlen(word), table);
+        
+        size_t pos;
+        int res = find_substring(text, strlen(text), word, strlen(word), table, &pos);
+        ASSERT_FORCE(res)
+        ASSERT_FORCE(pos == 3)
+    }
+    
+    {
+        char text[] = "aagagga";
+        char word[] = "aga";
+        size_t table[sizeof(word) - 1];
+        build_substring_backtrack_table_reverse(word, strlen(word), table);
+        
+        size_t pos;
+        int res = find_substring_reverse(text, strlen(text), word, strlen(word), table, &pos);
+        ASSERT_FORCE(res)
+        ASSERT_FORCE(pos == 1)
+    }
+    
+    return 0;
+}
diff --git a/external/badvpn_dns/fix_flex.php b/external/badvpn_dns/fix_flex.php
new file mode 100644
index 0000000..bd026d0
--- /dev/null
+++ b/external/badvpn_dns/fix_flex.php
@@ -0,0 +1,10 @@
+<?php
+
+$filename = $argv[1];
+$contents = file_get_contents($filename);
+if ($contents === FALSE) exit(1);
+$search = array("<inttypes.h>", "#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L");
+$replace = array("<stdint.h>", "#if 1");
+$contents = str_replace($search, $replace, $contents);
+$res = file_put_contents($filename, $contents);
+if ($res === FALSE) exit(1);
diff --git a/external/badvpn_dns/flooder/CMakeLists.txt b/external/badvpn_dns/flooder/CMakeLists.txt
new file mode 100644
index 0000000..36253ab
--- /dev/null
+++ b/external/badvpn_dns/flooder/CMakeLists.txt
@@ -0,0 +1,7 @@
+add_executable(badvpn-flooder flooder.c)
+target_link_libraries(badvpn-flooder system flow server_conection ${NSPR_LIBRARIES} ${NSS_LIBRARIES})
+
+install(
+    TARGETS badvpn-flooder
+    RUNTIME DESTINATION bin
+)
diff --git a/external/badvpn_dns/flooder/flooder.c b/external/badvpn_dns/flooder/flooder.c
new file mode 100644
index 0000000..1f3f05c
--- /dev/null
+++ b/external/badvpn_dns/flooder/flooder.c
@@ -0,0 +1,671 @@
+/**
+ * @file flooder.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <protocol/addr.h>
+#include <protocol/scproto.h>
+#include <misc/loglevel.h>
+#include <misc/version.h>
+#include <misc/nsskey.h>
+#include <misc/byteorder.h>
+#include <misc/loggers_string.h>
+#include <misc/open_standard_streams.h>
+#include <base/BLog.h>
+#include <system/BReactor.h>
+#include <system/BSignal.h>
+#include <system/BNetwork.h>
+#include <flow/SinglePacketBuffer.h>
+#include <flow/PacketProtoEncoder.h>
+#include <nspr_support/BSSLConnection.h>
+#include <server_connection/ServerConnection.h>
+
+#ifndef BADVPN_USE_WINAPI
+#include <base/BLog_syslog.h>
+#endif
+
+#include <flooder/flooder.h>
+
+#include <generated/blog_channel_flooder.h>
+
+#define LOGGER_STDOUT 1
+#define LOGGER_SYSLOG 2
+
+// command-line options
+struct {
+    int help;
+    int version;
+    int logger;
+    #ifndef BADVPN_USE_WINAPI
+    char *logger_syslog_facility;
+    char *logger_syslog_ident;
+    #endif
+    int loglevel;
+    int loglevels[BLOG_NUM_CHANNELS];
+    int ssl;
+    char *nssdb;
+    char *client_cert_name;
+    char *server_name;
+    char *server_addr;
+    peerid_t floods[MAX_FLOODS];
+    int num_floods;
+} options;
+
+// server address we connect to
+BAddr server_addr;
+
+// server name to use for SSL
+char server_name[256];
+
+// reactor
+BReactor ss;
+
+// client certificate if using SSL
+CERTCertificate *client_cert;
+
+// client private key if using SSL
+SECKEYPrivateKey *client_key;
+
+// server connection
+ServerConnection server;
+
+// whether server is ready
+int server_ready;
+
+// my ID, defined only after server_ready
+peerid_t my_id;
+
+// flooding output
+PacketRecvInterface flood_source;
+PacketProtoEncoder flood_encoder;
+SinglePacketBuffer flood_buffer;
+
+// whether we were asked for a packet and blocked
+int flood_blocking;
+
+// index of next peer to send packet too
+int flood_next;
+
+/**
+ * Cleans up everything that can be cleaned up from inside the event loop.
+ */
+static void terminate (void);
+
+/**
+ * Prints command line help.
+ */
+static void print_help (const char *name);
+
+/**
+ * Prints program name, version and copyright notice.
+ */
+static void print_version (void);
+
+/**
+ * Parses command line options into the options strucute.
+ *
+ * @return 1 on success, 0 on failure
+ */
+static int parse_arguments (int argc, char *argv[]);
+
+/**
+ * Processes command line options.
+ *
+ * @return 1 on success, 0 on failure
+ */
+static int resolve_arguments (void);
+
+/**
+ * Handler invoked when program termination is requested.
+ */
+static void signal_handler (void *unused);
+
+static void server_handler_error (void *user);
+static void server_handler_ready (void *user, peerid_t param_my_id, uint32_t ext_ip);
+static void server_handler_newclient (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len);
+static void server_handler_endclient (void *user, peerid_t peer_id);
+static void server_handler_message (void *user, peerid_t peer_id, uint8_t *data, int data_len);
+
+static void flood_source_handler_recv (void *user, uint8_t *data);
+
+int main (int argc, char *argv[])
+{
+    if (argc <= 0) {
+        return 1;
+    }
+    
+    // open standard streams
+    open_standard_streams();
+    
+    // parse command-line arguments
+    if (!parse_arguments(argc, argv)) {
+        fprintf(stderr, "Failed to parse arguments\n");
+        print_help(argv[0]);
+        goto fail0;
+    }
+    
+    // handle --help and --version
+    if (options.help) {
+        print_version();
+        print_help(argv[0]);
+        return 0;
+    }
+    if (options.version) {
+        print_version();
+        return 0;
+    }
+    
+    // initialize logger
+    switch (options.logger) {
+        case LOGGER_STDOUT:
+            BLog_InitStdout();
+            break;
+        #ifndef BADVPN_USE_WINAPI
+        case LOGGER_SYSLOG:
+            if (!BLog_InitSyslog(options.logger_syslog_ident, options.logger_syslog_facility)) {
+                fprintf(stderr, "Failed to initialize syslog logger\n");
+                goto fail0;
+            }
+            break;
+        #endif
+        default:
+            ASSERT(0);
+    }
+    
+    // configure logger channels
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        if (options.loglevels[i] >= 0) {
+            BLog_SetChannelLoglevel(i, options.loglevels[i]);
+        }
+        else if (options.loglevel >= 0) {
+            BLog_SetChannelLoglevel(i, options.loglevel);
+        }
+    }
+    
+    BLog(BLOG_NOTICE, "initializing "GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION);
+    
+    // initialize network
+    if (!BNetwork_GlobalInit()) {
+        BLog(BLOG_ERROR, "BNetwork_GlobalInit failed");
+        goto fail1;
+    }
+    
+    // init time
+    BTime_Init();
+    
+    // resolve addresses
+    if (!resolve_arguments()) {
+        BLog(BLOG_ERROR, "Failed to resolve arguments");
+        goto fail1;
+    }
+    
+    // init reactor
+    if (!BReactor_Init(&ss)) {
+        BLog(BLOG_ERROR, "BReactor_Init failed");
+        goto fail1;
+    }
+    
+    // setup signal handler
+    if (!BSignal_Init(&ss, signal_handler, NULL)) {
+        BLog(BLOG_ERROR, "BSignal_Init failed");
+        goto fail1a;
+    }
+    
+    if (options.ssl) {
+        // init NSPR
+        PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+        
+        // register local NSPR file types
+        if (!BSSLConnection_GlobalInit()) {
+            BLog(BLOG_ERROR, "BSSLConnection_GlobalInit failed");
+            goto fail3;
+        }
+        
+        // init NSS
+        if (NSS_Init(options.nssdb) != SECSuccess) {
+            BLog(BLOG_ERROR, "NSS_Init failed (%d)", (int)PR_GetError());
+            goto fail2;
+        }
+        
+        // set cipher policy
+        if (NSS_SetDomesticPolicy() != SECSuccess) {
+            BLog(BLOG_ERROR, "NSS_SetDomesticPolicy failed (%d)", (int)PR_GetError());
+            goto fail3;
+        }
+        
+        // init server cache
+        if (SSL_ConfigServerSessionIDCache(0, 0, 0, NULL) != SECSuccess) {
+            BLog(BLOG_ERROR, "SSL_ConfigServerSessionIDCache failed (%d)", (int)PR_GetError());
+            goto fail3;
+        }
+        
+        // open server certificate and private key
+        if (!open_nss_cert_and_key(options.client_cert_name, &client_cert, &client_key)) {
+            BLog(BLOG_ERROR, "Cannot open certificate and key");
+            goto fail4;
+        }
+    }
+    
+    // start connecting to server
+    if (!ServerConnection_Init(
+        &server, &ss, NULL, server_addr, SC_KEEPALIVE_INTERVAL, SERVER_BUFFER_MIN_PACKETS, options.ssl, 0, client_cert, client_key, server_name, NULL,
+        server_handler_error, server_handler_ready, server_handler_newclient, server_handler_endclient, server_handler_message
+    )) {
+        BLog(BLOG_ERROR, "ServerConnection_Init failed");
+        goto fail5;
+    }
+    
+    // set server not ready
+    server_ready = 0;
+    
+    // enter event loop
+    BLog(BLOG_NOTICE, "entering event loop");
+    BReactor_Exec(&ss);
+    
+    if (server_ready) {
+        ServerConnection_ReleaseBuffers(&server);
+        SinglePacketBuffer_Free(&flood_buffer);
+        PacketProtoEncoder_Free(&flood_encoder);
+        PacketRecvInterface_Free(&flood_source);
+    }
+    
+    ServerConnection_Free(&server);
+fail5:
+    if (options.ssl) {
+        CERT_DestroyCertificate(client_cert);
+        SECKEY_DestroyPrivateKey(client_key);
+fail4:
+        ASSERT_FORCE(SSL_ShutdownServerSessionIDCache() == SECSuccess)
+fail3:
+        SSL_ClearSessionCache();
+        ASSERT_FORCE(NSS_Shutdown() == SECSuccess)
+fail2:
+        ASSERT_FORCE(PR_Cleanup() == PR_SUCCESS)
+        PL_ArenaFinish();
+    }
+    
+    BSignal_Finish();
+fail1a:
+    BReactor_Free(&ss);
+fail1:
+    BLog(BLOG_NOTICE, "exiting");
+    BLog_Free();
+fail0:
+    DebugObjectGlobal_Finish();
+    
+    return 1;
+}
+
+void terminate (void)
+{
+    BLog(BLOG_NOTICE, "tearing down");
+    
+    // exit event loop
+    BReactor_Quit(&ss, 0);
+}
+
+void print_help (const char *name)
+{
+    printf(
+        "Usage:\n"
+        "    %s\n"
+        "        [--help]\n"
+        "        [--version]\n"
+        "        [--logger <"LOGGERS_STRING">]\n"
+        #ifndef BADVPN_USE_WINAPI
+        "        (logger=syslog?\n"
+        "            [--syslog-facility <string>]\n"
+        "            [--syslog-ident <string>]\n"
+        "        )\n"
+        #endif
+        "        [--loglevel <0-5/none/error/warning/notice/info/debug>]\n"
+        "        [--channel-loglevel <channel-name> <0-5/none/error/warning/notice/info/debug>] ...\n"
+        "        [--ssl --nssdb <string> --client-cert-name <string>]\n"
+        "        [--server-name <string>]\n"
+        "        --server-addr <addr>\n"
+        "        [--flood-id <id>] ...\n"
+        "Address format is a.b.c.d:port (IPv4) or [addr]:port (IPv6).\n",
+        name
+    );
+}
+
+void print_version (void)
+{
+    printf(GLOBAL_PRODUCT_NAME" "PROGRAM_NAME" "GLOBAL_VERSION"\n"GLOBAL_COPYRIGHT_NOTICE"\n");
+}
+
+int parse_arguments (int argc, char *argv[])
+{
+    if (argc <= 0) {
+        return 0;
+    }
+    
+    options.help = 0;
+    options.version = 0;
+    options.logger = LOGGER_STDOUT;
+    #ifndef BADVPN_USE_WINAPI
+    options.logger_syslog_facility = "daemon";
+    options.logger_syslog_ident = argv[0];
+    #endif
+    options.loglevel = -1;
+    for (int i = 0; i < BLOG_NUM_CHANNELS; i++) {
+        options.loglevels[i] = -1;
+    }
+    options.ssl = 0;
+    options.nssdb = NULL;
+    options.client_cert_name = NULL;
+    options.server_name = NULL;
+    options.server_addr = NULL;
+    options.num_floods = 0;
+    
+    int i;
+    for (i = 1; i < argc; i++) {
+        char *arg = argv[i];
+        if (!strcmp(arg, "--help")) {
+            options.help = 1;
+        }
+        else if (!strcmp(arg, "--version")) {
+            options.version = 1;
+        }
+        else if (!strcmp(arg, "--logger")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            char *arg2 = argv[i + 1];
+            if (!strcmp(arg2, "stdout")) {
+                options.logger = LOGGER_STDOUT;
+            }
+            #ifndef BADVPN_USE_WINAPI
+            else if (!strcmp(arg2, "syslog")) {
+                options.logger = LOGGER_SYSLOG;
+            }
+            #endif
+            else {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        #ifndef BADVPN_USE_WINAPI
+        else if (!strcmp(arg, "--syslog-facility")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.logger_syslog_facility = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--syslog-ident")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.logger_syslog_ident = argv[i + 1];
+            i++;
+        }
+        #endif
+        else if (!strcmp(arg, "--loglevel")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if ((options.loglevel = parse_loglevel(argv[i + 1])) < 0) {
+                fprintf(stderr, "%s: wrong argument\n", arg);
+                return 0;
+            }
+            i++;
+        }
+        else if (!strcmp(arg, "--channel-loglevel")) {
+            if (2 >= argc - i) {
+                fprintf(stderr, "%s: requires two arguments\n", arg);
+                return 0;
+            }
+            int channel = BLogGlobal_GetChannelByName(argv[i + 1]);
+            if (channel < 0) {
+                fprintf(stderr, "%s: wrong channel argument\n", arg);
+                return 0;
+            }
+            int loglevel = parse_loglevel(argv[i + 2]);
+            if (loglevel < 0) {
+                fprintf(stderr, "%s: wrong loglevel argument\n", arg);
+                return 0;
+            }
+            options.loglevels[channel] = loglevel;
+            i += 2;
+        }
+        else if (!strcmp(arg, "--ssl")) {
+            options.ssl = 1;
+        }
+        else if (!strcmp(arg, "--nssdb")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.nssdb = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--client-cert-name")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.client_cert_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--server-name")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.server_name = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--server-addr")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            options.server_addr = argv[i + 1];
+            i++;
+        }
+        else if (!strcmp(arg, "--flood-id")) {
+            if (1 >= argc - i) {
+                fprintf(stderr, "%s: requires an argument\n", arg);
+                return 0;
+            }
+            if (options.num_floods == MAX_FLOODS) {
+                fprintf(stderr, "%s: too many\n", arg);
+                return 0;
+            }
+            options.floods[options.num_floods] = atoi(argv[i + 1]);
+            options.num_floods++;
+            i++;
+        }
+        else {
+            fprintf(stderr, "unknown option: %s\n", arg);
+            return 0;
+        }
+    }
+    
+    if (options.help || options.version) {
+        return 1;
+    }
+    
+    if (options.ssl != !!options.nssdb) {
+        fprintf(stderr, "False: --ssl <=> --nssdb\n");
+        return 0;
+    }
+    
+    if (options.ssl != !!options.client_cert_name) {
+        fprintf(stderr, "False: --ssl <=> --client-cert-name\n");
+        return 0;
+    }
+    
+    if (!options.server_addr) {
+        fprintf(stderr, "False: --server-addr\n");
+        return 0;
+    }
+    
+    return 1;
+}
+
+int resolve_arguments (void)
+{
+    // resolve server address
+    ASSERT(options.server_addr)
+    if (!BAddr_Parse(&server_addr, options.server_addr, server_name, sizeof(server_name))) {
+        BLog(BLOG_ERROR, "server addr: BAddr_Parse failed");
+        return 0;
+    }
+    if (!addr_supported(server_addr)) {
+        BLog(BLOG_ERROR, "server addr: not supported");
+        return 0;
+    }
+    
+    // override server name if requested
+    if (options.server_name) {
+        if (strlen(options.server_name) >= sizeof(server_name)) {
+            BLog(BLOG_ERROR, "server name: too long");
+            return 0;
+        }
+        strcpy(server_name, options.server_name);
+    }
+    
+    return 1;
+}
+
+void signal_handler (void *unused)
+{
+    BLog(BLOG_NOTICE, "termination requested");
+    
+    terminate();
+}
+
+void server_handler_error (void *user)
+{
+    BLog(BLOG_ERROR, "server connection failed, exiting");
+    
+    terminate();
+}
+
+void server_handler_ready (void *user, peerid_t param_my_id, uint32_t ext_ip)
+{
+    ASSERT(!server_ready)
+    
+    // remember our ID
+    my_id = param_my_id;
+    
+    // init flooding
+    
+    // init source
+    PacketRecvInterface_Init(&flood_source, SC_MAX_ENC, flood_source_handler_recv, NULL, BReactor_PendingGroup(&ss));
+    
+    // init encoder
+    PacketProtoEncoder_Init(&flood_encoder, &flood_source, BReactor_PendingGroup(&ss));
+    
+    // init buffer
+    if (!SinglePacketBuffer_Init(&flood_buffer, PacketProtoEncoder_GetOutput(&flood_encoder), ServerConnection_GetSendInterface(&server), BReactor_PendingGroup(&ss))) {
+        BLog(BLOG_ERROR, "SinglePacketBuffer_Init failed, exiting");
+        goto fail1;
+    }
+    
+    // set not blocking
+    flood_blocking = 0;
+    
+    // set server ready
+    server_ready = 1;
+    
+    BLog(BLOG_INFO, "server: ready, my ID is %d", (int)my_id);
+    
+    return;
+    
+fail1:
+    PacketProtoEncoder_Free(&flood_encoder);
+    PacketRecvInterface_Free(&flood_source);
+    terminate();
+}
+
+void server_handler_newclient (void *user, peerid_t peer_id, int flags, const uint8_t *cert, int cert_len)
+{
+    ASSERT(server_ready)
+    
+    BLog(BLOG_INFO, "newclient %d", (int)peer_id);
+}
+
+void server_handler_endclient (void *user, peerid_t peer_id)
+{
+    ASSERT(server_ready)
+    
+    BLog(BLOG_INFO, "endclient %d", (int)peer_id);
+}
+
+void server_handler_message (void *user, peerid_t peer_id, uint8_t *data, int data_len)
+{
+    ASSERT(server_ready)
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= SC_MAX_MSGLEN)
+    
+    BLog(BLOG_INFO, "message from %d", (int)peer_id);
+}
+
+void flood_source_handler_recv (void *user, uint8_t *data)
+{
+    ASSERT(server_ready)
+    ASSERT(!flood_blocking)
+    if (options.num_floods > 0) {
+        ASSERT(flood_next >= 0)
+        ASSERT(flood_next < options.num_floods)
+    }
+    
+    if (options.num_floods == 0) {
+        flood_blocking = 1;
+        return;
+    }
+    
+    peerid_t peer_id = options.floods[flood_next];
+    flood_next = (flood_next + 1) % options.num_floods;
+    
+    BLog(BLOG_INFO, "message to %d", (int)peer_id);
+    
+    struct sc_header header;
+    header.type = SCID_OUTMSG;
+    memcpy(data, &header, sizeof(header));
+    
+    struct sc_client_outmsg omsg;
+    omsg.clientid = htol16(peer_id);
+    memcpy(data + sizeof(header), &omsg, sizeof(omsg));
+    
+    memset(data + sizeof(struct sc_header) + sizeof(struct sc_client_outmsg), 0, SC_MAX_MSGLEN);
+    
+    PacketRecvInterface_Done(&flood_source, sizeof(struct sc_header) + sizeof(struct sc_client_outmsg) + SC_MAX_MSGLEN);
+}
diff --git a/external/badvpn_dns/flooder/flooder.h b/external/badvpn_dns/flooder/flooder.h
new file mode 100644
index 0000000..c8b8443
--- /dev/null
+++ b/external/badvpn_dns/flooder/flooder.h
@@ -0,0 +1,37 @@
+/**
+ * @file flooder.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// name of the program
+#define PROGRAM_NAME "flooder"
+
+// server output buffer size
+#define SERVER_BUFFER_MIN_PACKETS 200
+
+// maximum number of peers to flood
+#define MAX_FLOODS 64
diff --git a/external/badvpn_dns/flow/BufferWriter.c b/external/badvpn_dns/flow/BufferWriter.c
new file mode 100644
index 0000000..b0e4129
--- /dev/null
+++ b/external/badvpn_dns/flow/BufferWriter.c
@@ -0,0 +1,112 @@
+/**
+ * @file BufferWriter.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <misc/debug.h>
+
+#include <flow/BufferWriter.h>
+
+static void output_handler_recv (BufferWriter *o, uint8_t *data)
+{
+    ASSERT(!o->out_have)
+    
+    // set output packet
+    o->out_have = 1;
+    o->out = data;
+}
+
+void BufferWriter_Init (BufferWriter *o, int mtu, BPendingGroup *pg)
+{
+    ASSERT(mtu >= 0)
+    
+    // init output
+    PacketRecvInterface_Init(&o->recv_interface, mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    // set no output packet
+    o->out_have = 0;
+    
+    DebugObject_Init(&o->d_obj);
+    #ifndef NDEBUG
+    o->d_mtu = mtu;
+    o->d_writing = 0;
+    #endif
+}
+
+void BufferWriter_Free (BufferWriter *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free output
+    PacketRecvInterface_Free(&o->recv_interface);
+}
+
+PacketRecvInterface * BufferWriter_GetOutput (BufferWriter *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->recv_interface;
+}
+
+int BufferWriter_StartPacket (BufferWriter *o, uint8_t **buf)
+{
+    ASSERT(!o->d_writing)
+    DebugObject_Access(&o->d_obj);
+    
+    if (!o->out_have) {
+        return 0;
+    }
+    
+    if (buf) {
+        *buf = o->out;
+    }
+    
+    #ifndef NDEBUG
+    o->d_writing = 1;
+    #endif
+    
+    return 1;
+}
+
+void BufferWriter_EndPacket (BufferWriter *o, int len)
+{
+    ASSERT(len >= 0)
+    ASSERT(len <= o->d_mtu)
+    ASSERT(o->out_have)
+    ASSERT(o->d_writing)
+    DebugObject_Access(&o->d_obj);
+    
+    // set no output packet
+    o->out_have = 0;
+    
+    // finish packet
+    PacketRecvInterface_Done(&o->recv_interface, len);
+    
+    #ifndef NDEBUG
+    o->d_writing = 0;
+    #endif
+}
diff --git a/external/badvpn_dns/flow/BufferWriter.h b/external/badvpn_dns/flow/BufferWriter.h
new file mode 100644
index 0000000..6b6a9c4
--- /dev/null
+++ b/external/badvpn_dns/flow/BufferWriter.h
@@ -0,0 +1,107 @@
+/**
+ * @file BufferWriter.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object for writing packets to a {@link PacketRecvInterface} client
+ * in a best-effort fashion.
+ */
+
+#ifndef BADVPN_FLOW_BUFFERWRITER_H
+#define BADVPN_FLOW_BUFFERWRITER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * Object for writing packets to a {@link PacketRecvInterface} client
+ * in a best-effort fashion.
+ */
+typedef struct {
+    PacketRecvInterface recv_interface;
+    int out_have;
+    uint8_t *out;
+    DebugObject d_obj;
+    #ifndef NDEBUG
+    int d_mtu;
+    int d_writing;
+    #endif
+} BufferWriter;
+
+/**
+ * Initializes the object.
+ * The object is initialized in not writing state.
+ *
+ * @param o the object
+ * @param mtu maximum input packet length
+ * @param pg pending group
+ */
+void BufferWriter_Init (BufferWriter *o, int mtu, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void BufferWriter_Free (BufferWriter *o);
+
+/**
+ * Returns the output interface.
+ *
+ * @param o the object
+ * @return output interface
+ */
+PacketRecvInterface * BufferWriter_GetOutput (BufferWriter *o);
+
+/**
+ * Attempts to provide a memory location for writing a packet.
+ * The object must be in not writing state.
+ * On success, the object enters writing state.
+ * 
+ * @param o the object
+ * @param buf if not NULL, on success, the memory location will be stored here.
+ *            It will have space for MTU bytes.
+ * @return 1 on success, 0 on failure
+ */
+int BufferWriter_StartPacket (BufferWriter *o, uint8_t **buf) WARN_UNUSED;
+
+/**
+ * Submits a packet written to the buffer.
+ * The object must be in writing state.
+ * Yhe object enters not writing state.
+ * 
+ * @param o the object
+ * @param len length of the packet that was written. Must be >=0 and
+ *            <=MTU.
+ */
+void BufferWriter_EndPacket (BufferWriter *o, int len);
+
+#endif
diff --git a/external/badvpn_dns/flow/CMakeLists.txt b/external/badvpn_dns/flow/CMakeLists.txt
new file mode 100644
index 0000000..6cd82f6
--- /dev/null
+++ b/external/badvpn_dns/flow/CMakeLists.txt
@@ -0,0 +1,31 @@
+set(FLOW_SOURCES
+    PacketPassFairQueue.c
+    PacketPassPriorityQueue.c
+    PacketPassConnector.c
+    PacketRecvConnector.c
+    StreamRecvConnector.c
+    PacketRecvBlocker.c
+    PacketPassNotifier.c
+    PacketBuffer.c
+    SinglePacketBuffer.c
+    PacketCopier.c
+    PacketStreamSender.c
+    PacketProtoEncoder.c
+    PacketProtoDecoder.c
+    PacketProtoFlow.c
+    SinglePacketSender.c
+    BufferWriter.c
+    PacketPassInterface.c
+    PacketRecvInterface.c
+    StreamPassInterface.c
+    StreamRecvInterface.c
+    RouteBuffer.c
+    PacketRouter.c
+    LineBuffer.c
+    SingleStreamSender.c
+    SingleStreamReceiver.c
+    StreamPacketSender.c
+    StreamPassConnector.c
+    PacketPassFifoQueue.c
+)
+badvpn_add_library(flow "base" "" "${FLOW_SOURCES}")
diff --git a/external/badvpn_dns/flow/LineBuffer.c b/external/badvpn_dns/flow/LineBuffer.c
new file mode 100644
index 0000000..15c9969
--- /dev/null
+++ b/external/badvpn_dns/flow/LineBuffer.c
@@ -0,0 +1,140 @@
+/**
+ * @file LineBuffer.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <base/BLog.h>
+
+#include <flow/LineBuffer.h>
+
+#include <generated/blog_channel_LineBuffer.h>
+
+static void input_handler_done (LineBuffer *o, int data_len)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(data_len > 0)
+    ASSERT(data_len <= o->buf_size - o->buf_used)
+    
+    // update buffer
+    o->buf_used += data_len;
+    
+    // look for newline
+    int i;
+    for (i = o->buf_used - data_len; i < o->buf_used; i++) {
+        if (o->buf[i] == o->nl_char) {
+            break;
+        }
+    }
+    
+    if (i < o->buf_used || o->buf_used == o->buf_size) {
+        if (i == o->buf_used) {
+            BLog(BLOG_WARNING, "line too long");
+        }
+        
+        // pass to output
+        o->buf_consumed = (i < o->buf_used ? i + 1 : i);
+        PacketPassInterface_Sender_Send(o->output, o->buf, o->buf_consumed);
+    } else {
+        // receive more data
+        StreamRecvInterface_Receiver_Recv(o->input, o->buf + o->buf_used, o->buf_size - o->buf_used);
+    }
+}
+
+static void output_handler_done (LineBuffer *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->buf_consumed > 0)
+    ASSERT(o->buf_consumed <= o->buf_used)
+    
+    // update buffer
+    memmove(o->buf, o->buf + o->buf_consumed, o->buf_used - o->buf_consumed);
+    o->buf_used -= o->buf_consumed;
+    
+    // look for newline
+    int i;
+    for (i = 0; i < o->buf_used; i++) {
+        if (o->buf[i] == o->nl_char) {
+            break;
+        }
+    }
+    
+    if (i < o->buf_used || o->buf_used == o->buf_size) {
+        // pass to output
+        o->buf_consumed = (i < o->buf_used ? i + 1 : i);
+        PacketPassInterface_Sender_Send(o->output, o->buf, o->buf_consumed);
+    } else {
+        // receive more data
+        StreamRecvInterface_Receiver_Recv(o->input, o->buf + o->buf_used, o->buf_size - o->buf_used);
+    }
+}
+
+int LineBuffer_Init (LineBuffer *o, StreamRecvInterface *input, PacketPassInterface *output, int buf_size, uint8_t nl_char)
+{
+    ASSERT(buf_size > 0)
+    ASSERT(PacketPassInterface_GetMTU(output) >= buf_size)
+    
+    // init arguments
+    o->input = input;
+    o->output = output;
+    o->buf_size = buf_size;
+    o->nl_char = nl_char;
+    
+    // init input
+    StreamRecvInterface_Receiver_Init(o->input, (StreamRecvInterface_handler_done)input_handler_done, o);
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // set buffer empty
+    o->buf_used = 0;
+    
+    // allocate buffer
+    if (!(o->buf = (uint8_t *)malloc(o->buf_size))) {
+        BLog(BLOG_ERROR, "malloc failed");
+        goto fail0;
+    }
+    
+    // start receiving
+    StreamRecvInterface_Receiver_Recv(o->input, o->buf, o->buf_size);
+    
+    DebugObject_Init(&o->d_obj);
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void LineBuffer_Free (LineBuffer *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free buffer
+    free(o->buf);
+}
diff --git a/external/badvpn_dns/flow/LineBuffer.h b/external/badvpn_dns/flow/LineBuffer.h
new file mode 100644
index 0000000..6e15e32
--- /dev/null
+++ b/external/badvpn_dns/flow/LineBuffer.h
@@ -0,0 +1,54 @@
+/**
+ * @file LineBuffer.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_FLOW_LINEBUFFER_H
+#define BADVPN_FLOW_LINEBUFFER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <flow/StreamRecvInterface.h>
+#include <flow/PacketPassInterface.h>
+
+typedef struct {
+    StreamRecvInterface *input;
+    PacketPassInterface *output;
+    int buf_size;
+    uint8_t nl_char;
+    int buf_used;
+    uint8_t *buf;
+    int buf_consumed;
+    DebugObject d_obj;
+} LineBuffer;
+
+int LineBuffer_Init (LineBuffer *o, StreamRecvInterface *input, PacketPassInterface *output, int buf_size, uint8_t nl_char) WARN_UNUSED;
+void LineBuffer_Free (LineBuffer *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketBuffer.c b/external/badvpn_dns/flow/PacketBuffer.c
new file mode 100644
index 0000000..30afef6
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketBuffer.c
@@ -0,0 +1,131 @@
+/**
+ * @file PacketBuffer.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/balloc.h>
+
+#include <flow/PacketBuffer.h>
+
+static void input_handler_done (PacketBuffer *buf, int in_len);
+static void output_handler_done (PacketBuffer *buf);
+
+void input_handler_done (PacketBuffer *buf, int in_len)
+{
+    ASSERT(in_len >= 0)
+    ASSERT(in_len <= buf->input_mtu)
+    DebugObject_Access(&buf->d_obj);
+    
+    // remember if buffer is empty
+    int was_empty = (buf->buf.output_avail < 0);
+    
+    // submit packet to buffer
+    ChunkBuffer2_SubmitPacket(&buf->buf, in_len);
+    
+    // if there is space, schedule receive
+    if (buf->buf.input_avail >= buf->input_mtu) {
+        PacketRecvInterface_Receiver_Recv(buf->input, buf->buf.input_dest);
+    }
+    
+    // if buffer was empty, schedule send
+    if (was_empty) {
+        PacketPassInterface_Sender_Send(buf->output, buf->buf.output_dest, buf->buf.output_avail);
+    }
+}
+
+void output_handler_done (PacketBuffer *buf)
+{
+    DebugObject_Access(&buf->d_obj);
+    
+    // remember if buffer is full
+    int was_full = (buf->buf.input_avail < buf->input_mtu);
+    
+    // remove packet from buffer
+    ChunkBuffer2_ConsumePacket(&buf->buf);
+    
+    // if buffer was full and there is space, schedule receive
+    if (was_full && buf->buf.input_avail >= buf->input_mtu) {
+        PacketRecvInterface_Receiver_Recv(buf->input, buf->buf.input_dest);
+    }
+    
+    // if there is more data, schedule send
+    if (buf->buf.output_avail >= 0) {
+        PacketPassInterface_Sender_Send(buf->output, buf->buf.output_dest, buf->buf.output_avail);
+    }
+}
+
+int PacketBuffer_Init (PacketBuffer *buf, PacketRecvInterface *input, PacketPassInterface *output, int num_packets, BPendingGroup *pg)
+{
+    ASSERT(PacketPassInterface_GetMTU(output) >= PacketRecvInterface_GetMTU(input))
+    ASSERT(num_packets > 0)
+    
+    // init arguments
+    buf->input = input;
+    buf->output = output;
+    
+    // init input
+    PacketRecvInterface_Receiver_Init(buf->input, (PacketRecvInterface_handler_done)input_handler_done, buf);
+    
+    // set input MTU
+    buf->input_mtu = PacketRecvInterface_GetMTU(buf->input);
+    
+    // init output
+    PacketPassInterface_Sender_Init(buf->output, (PacketPassInterface_handler_done)output_handler_done, buf);
+    
+    // allocate buffer
+    int num_blocks = ChunkBuffer2_calc_blocks(buf->input_mtu, num_packets);
+    if (num_blocks < 0) {
+        goto fail0;
+    }
+    if (!(buf->buf_data = (struct ChunkBuffer2_block *)BAllocArray(num_blocks, sizeof(buf->buf_data[0])))) {
+        goto fail0;
+    }
+    
+    // init buffer
+    ChunkBuffer2_Init(&buf->buf, buf->buf_data, num_blocks, buf->input_mtu);
+    
+    // schedule receive
+    PacketRecvInterface_Receiver_Recv(buf->input, buf->buf.input_dest);
+    
+    DebugObject_Init(&buf->d_obj);
+    
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void PacketBuffer_Free (PacketBuffer *buf)
+{
+    DebugObject_Free(&buf->d_obj);
+    
+    // free buffer
+    BFree(buf->buf_data);
+}
diff --git a/external/badvpn_dns/flow/PacketBuffer.h b/external/badvpn_dns/flow/PacketBuffer.h
new file mode 100644
index 0000000..6daab69
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketBuffer.h
@@ -0,0 +1,77 @@
+/**
+ * @file PacketBuffer.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Packet buffer with {@link PacketRecvInterface} input and {@link PacketPassInterface} output.
+ */
+
+#ifndef BADVPN_FLOW_PACKETBUFFER_H
+#define BADVPN_FLOW_PACKETBUFFER_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <base/DebugObject.h>
+#include <structure/ChunkBuffer2.h>
+#include <flow/PacketRecvInterface.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * Packet buffer with {@link PacketRecvInterface} input and {@link PacketPassInterface} output.
+ */
+typedef struct {
+    DebugObject d_obj;
+    PacketRecvInterface *input;
+    int input_mtu;
+    PacketPassInterface *output;
+    struct ChunkBuffer2_block *buf_data;
+    ChunkBuffer2 buf;
+} PacketBuffer;
+
+/**
+ * Initializes the buffer.
+ * Output MTU must be >= input MTU.
+ *
+ * @param buf the object
+ * @param input input interface
+ * @param output output interface
+ * @param num_packets minimum number of packets the buffer must hold. Must be >0.
+ * @param pg pending group
+ * @return 1 on success, 0 on failure
+ */
+int PacketBuffer_Init (PacketBuffer *buf, PacketRecvInterface *input, PacketPassInterface *output, int num_packets, BPendingGroup *pg) WARN_UNUSED;
+
+/**
+ * Frees the buffer.
+ *
+ * @param buf the object
+ */
+void PacketBuffer_Free (PacketBuffer *buf);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketCopier.c b/external/badvpn_dns/flow/PacketCopier.c
new file mode 100644
index 0000000..f4c7126
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketCopier.c
@@ -0,0 +1,136 @@
+/**
+ * @file PacketCopier.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include <misc/debug.h>
+
+#include <flow/PacketCopier.h>
+
+static void input_handler_send (PacketCopier *o, uint8_t *data, int data_len)
+{
+    ASSERT(o->in_len == -1)
+    ASSERT(data_len >= 0)
+    DebugObject_Access(&o->d_obj);
+    
+    if (!o->out_have) {
+        o->in_len = data_len;
+        o->in = data;
+        return;
+    }
+    
+    memcpy(o->out, data, data_len);
+    
+    // finish input packet
+    PacketPassInterface_Done(&o->input);
+    
+    // finish output packet
+    PacketRecvInterface_Done(&o->output, data_len);
+    
+    o->out_have = 0;
+}
+
+static void input_handler_requestcancel (PacketCopier *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(!o->out_have)
+    DebugObject_Access(&o->d_obj);
+    
+    // finish input packet
+    PacketPassInterface_Done(&o->input);
+    
+    o->in_len = -1;
+}
+
+static void output_handler_recv (PacketCopier *o, uint8_t *data)
+{
+    ASSERT(!o->out_have)
+    DebugObject_Access(&o->d_obj);
+    
+    if (o->in_len < 0) {
+        o->out_have = 1;
+        o->out = data;
+        return;
+    }
+    
+    memcpy(data, o->in, o->in_len);
+    
+    // finish input packet
+    PacketPassInterface_Done(&o->input);
+    
+    // finish output packet
+    PacketRecvInterface_Done(&o->output, o->in_len);
+    
+    o->in_len = -1;
+}
+
+void PacketCopier_Init (PacketCopier *o, int mtu, BPendingGroup *pg)
+{
+    ASSERT(mtu >= 0)
+    
+    // init input
+    PacketPassInterface_Init(&o->input, mtu, (PacketPassInterface_handler_send)input_handler_send, o, pg);
+    PacketPassInterface_EnableCancel(&o->input, (PacketPassInterface_handler_requestcancel)input_handler_requestcancel);
+    
+    // init output
+    PacketRecvInterface_Init(&o->output, mtu, (PacketRecvInterface_handler_recv)output_handler_recv, o, pg);
+    
+    // set no input packet
+    o->in_len = -1;
+    
+    // set no output packet
+    o->out_have = 0;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void PacketCopier_Free (PacketCopier *o)
+{
+    DebugObject_Free(&o->d_obj);
+
+    // free output
+    PacketRecvInterface_Free(&o->output);
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+PacketPassInterface * PacketCopier_GetInput (PacketCopier *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
+
+PacketRecvInterface * PacketCopier_GetOutput (PacketCopier *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->output;
+}
diff --git a/external/badvpn_dns/flow/PacketCopier.h b/external/badvpn_dns/flow/PacketCopier.h
new file mode 100644
index 0000000..9b8ba86
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketCopier.h
@@ -0,0 +1,90 @@
+/**
+ * @file PacketCopier.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Object which copies packets.
+ */
+
+#ifndef BADVPN_FLOW_PACKETCOPIER_H
+#define BADVPN_FLOW_PACKETCOPIER_H
+
+#include <stdint.h>
+
+#include <flow/PacketPassInterface.h>
+#include <flow/PacketRecvInterface.h>
+
+/**
+ * Object which copies packets.
+ * Input is via {@link PacketPassInterface}.
+ * Output is via {@link PacketRecvInterface}.
+ */
+typedef struct {
+    DebugObject d_obj;
+    PacketPassInterface input;
+    PacketRecvInterface output;
+    int in_len;
+    uint8_t *in;
+    int out_have;
+    uint8_t *out;
+} PacketCopier;
+
+/**
+ * Initializes the object.
+ * 
+ * @param o the object
+ * @param mtu maximum packet size. Must be >=0.
+ * @param pg pending group
+ */
+void PacketCopier_Init (PacketCopier *o, int mtu, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ * 
+ * @param o the object
+ */
+void PacketCopier_Free (PacketCopier *o);
+
+/**
+ * Returns the input interface.
+ * The MTU of the interface will as in {@link PacketCopier_Init}.
+ * The interface will support cancel functionality.
+ * 
+ * @return input interface
+ */
+PacketPassInterface * PacketCopier_GetInput (PacketCopier *o);
+
+/**
+ * Returns the output interface.
+ * The MTU of the interface will be as in {@link PacketCopier_Init}.
+ * 
+ * @return output interface
+ */
+PacketRecvInterface * PacketCopier_GetOutput (PacketCopier *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketPassConnector.c b/external/badvpn_dns/flow/PacketPassConnector.c
new file mode 100644
index 0000000..1a10326
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassConnector.c
@@ -0,0 +1,125 @@
+/**
+ * @file PacketPassConnector.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+
+#include <flow/PacketPassConnector.h>
+
+static void input_handler_send (PacketPassConnector *o, uint8_t *data, int data_len)
+{
+    ASSERT(data_len >= 0)
+    ASSERT(data_len <= o->input_mtu)
+    ASSERT(o->in_len == -1)
+    DebugObject_Access(&o->d_obj);
+    
+    // remember input packet
+    o->in_len = data_len;
+    o->in = data;
+    
+    if (o->output) {
+        // schedule send
+        PacketPassInterface_Sender_Send(o->output, o->in, o->in_len);
+    }
+}
+
+static void output_handler_done (PacketPassConnector *o)
+{
+    ASSERT(o->in_len >= 0)
+    ASSERT(o->output)
+    DebugObject_Access(&o->d_obj);
+    
+    // have no input packet
+    o->in_len = -1;
+    
+    // allow input to send more packets
+    PacketPassInterface_Done(&o->input);
+}
+
+void PacketPassConnector_Init (PacketPassConnector *o, int mtu, BPendingGroup *pg)
+{
+    ASSERT(mtu >= 0)
+    
+    // init arguments
+    o->input_mtu = mtu;
+    
+    // init input
+    PacketPassInterface_Init(&o->input, o->input_mtu, (PacketPassInterface_handler_send)input_handler_send, o, pg);
+    
+    // have no input packet
+    o->in_len = -1;
+    
+    // have no output
+    o->output = NULL;
+    
+    DebugObject_Init(&o->d_obj);
+}
+
+void PacketPassConnector_Free (PacketPassConnector *o)
+{
+    DebugObject_Free(&o->d_obj);
+    
+    // free input
+    PacketPassInterface_Free(&o->input);
+}
+
+PacketPassInterface * PacketPassConnector_GetInput (PacketPassConnector *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    return &o->input;
+}
+
+void PacketPassConnector_ConnectOutput (PacketPassConnector *o, PacketPassInterface *output)
+{
+    ASSERT(!o->output)
+    ASSERT(PacketPassInterface_GetMTU(output) >= o->input_mtu)
+    DebugObject_Access(&o->d_obj);
+    
+    // set output
+    o->output = output;
+    
+    // init output
+    PacketPassInterface_Sender_Init(o->output, (PacketPassInterface_handler_done)output_handler_done, o);
+    
+    // if we have an input packet, schedule send
+    if (o->in_len >= 0) {
+        PacketPassInterface_Sender_Send(o->output, o->in, o->in_len);
+    }
+}
+
+void PacketPassConnector_DisconnectOutput (PacketPassConnector *o)
+{
+    ASSERT(o->output)
+    DebugObject_Access(&o->d_obj);
+    
+    // set no output
+    o->output = NULL;
+}
diff --git a/external/badvpn_dns/flow/PacketPassConnector.h b/external/badvpn_dns/flow/PacketPassConnector.h
new file mode 100644
index 0000000..1a2cc6c
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassConnector.h
@@ -0,0 +1,102 @@
+/**
+ * @file PacketPassConnector.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * A {@link PacketPassInterface} layer which allows the output to be
+ * connected and disconnected on the fly.
+ */
+
+#ifndef BADVPN_FLOW_PACKETPASSCONNECTOR_H
+#define BADVPN_FLOW_PACKETPASSCONNECTOR_H
+
+#include <stdint.h>
+
+#include <base/DebugObject.h>
+#include <flow/PacketPassInterface.h>
+
+/**
+ * A {@link PacketPassInterface} layer which allows the output to be
+ * connected and disconnected on the fly.
+ */
+typedef struct {
+    PacketPassInterface input;
+    int input_mtu;
+    int in_len;
+    uint8_t *in;
+    PacketPassInterface *output;
+    DebugObject d_obj;
+} PacketPassConnector;
+
+/**
+ * Initializes the object.
+ * The object is initialized in not connected state.
+ *
+ * @param o the object
+ * @param mtu maximum input packet size. Must be >=0.
+ * @param pg pending group
+ */
+void PacketPassConnector_Init (PacketPassConnector *o, int mtu, BPendingGroup *pg);
+
+/**
+ * Frees the object.
+ *
+ * @param o the object
+ */
+void PacketPassConnector_Free (PacketPassConnector *o);
+
+/**
+ * Returns the input interface.
+ * The MTU of the interface will be as in {@link PacketPassConnector_Init}.
+ *
+ * @param o the object
+ * @return input interface
+ */
+PacketPassInterface * PacketPassConnector_GetInput (PacketPassConnector *o);
+
+/**
+ * Connects output.
+ * The object must be in not connected state.
+ * The object enters connected state.
+ *
+ * @param o the object
+ * @param output output to connect. Its MTU must be >= MTU specified in
+ *               {@link PacketPassConnector_Init}.
+ */
+void PacketPassConnector_ConnectOutput (PacketPassConnector *o, PacketPassInterface *output);
+
+/**
+ * Disconnects output.
+ * The object must be in connected state.
+ * The object enters not connected state.
+ *
+ * @param o the object
+ */
+void PacketPassConnector_DisconnectOutput (PacketPassConnector *o);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketPassFairQueue.c b/external/badvpn_dns/flow/PacketPassFairQueue.c
new file mode 100644
index 0000000..bbfafe0
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassFairQueue.c
@@ -0,0 +1,405 @@
+/**
+ * @file PacketPassFairQueue.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+
+#include <misc/debug.h>
+#include <misc/offset.h>
+#include <misc/minmax.h>
+#include <misc/compare.h>
+
+#include <flow/PacketPassFairQueue.h>
+
+static int compare_flows (PacketPassFairQueueFlow *f1, PacketPassFairQueueFlow *f2)
+{
+    int cmp = B_COMPARE(f1->time, f2->time);
+    if (cmp) {
+        return cmp;
+    }
+    
+    return B_COMPARE((uintptr_t)f1, (uintptr_t)f2);
+}
+
+#include "PacketPassFairQueue_tree.h"
+#include <structure/SAvl_impl.h>
+
+static uint64_t get_current_time (PacketPassFairQueue *m)
+{
+    if (m->sending_flow) {
+        return m->sending_flow->time;
+    }
+    
+    uint64_t time = 0; // to remove warning
+    int have = 0;
+    
+    PacketPassFairQueueFlow *first_flow = PacketPassFairQueue__Tree_GetFirst(&m->queued_tree, 0);
+    if (first_flow) {
+        ASSERT(first_flow->is_queued)
+        
+        time = first_flow->time;
+        have = 1;
+    }
+    
+    if (m->previous_flow) {
+        if (!have || m->previous_flow->time < time) {
+            time = m->previous_flow->time;
+            have = 1;
+        }
+    }
+    
+    return (have ? time : 0);
+}
+
+static void increment_sent_flow (PacketPassFairQueueFlow *flow, uint64_t amount)
+{
+    PacketPassFairQueue *m = flow->m;
+    
+    ASSERT(amount <= FAIRQUEUE_MAX_TIME)
+    ASSERT(!flow->is_queued)
+    ASSERT(!m->sending_flow)
+    
+    // does time overflow?
+    if (amount > FAIRQUEUE_MAX_TIME - flow->time) {
+        // get time to subtract
+        uint64_t subtract;
+        PacketPassFairQueueFlow *first_flow = PacketPassFairQueue__Tree_GetFirst(&m->queued_tree, 0);
+        if (!first_flow) {
+            subtract = flow->time;
+        } else {
+            ASSERT(first_flow->is_queued)
+            subtract = first_flow->time;
+        }
+        
+        // subtract time from all flows
+        for (LinkedList1Node *list_node = LinkedList1_GetFirst(&m->flows_list); list_node; list_node = LinkedList1Node_Next(list_node)) {
+            PacketPassFairQueueFlow *someflow = UPPER_OBJECT(list_node, PacketPassFairQueueFlow, list_node);
+            
+            // don't subtract more time than there is, except for the just finished flow,
+            // where we allow time to underflow and then overflow to the correct value after adding to it
+            if (subtract > someflow->time && someflow != flow) {
+                ASSERT(!someflow->is_queued)
+                someflow->time = 0;
+            } else {
+                someflow->time -= subtract;
+            }
+        }
+    }
+    
+    // add time to flow
+    flow->time += amount;
+}
+
+static void schedule (PacketPassFairQueue *m)
+{
+    ASSERT(!m->sending_flow)
+    ASSERT(!m->previous_flow)
+    ASSERT(!m->freeing)
+    ASSERT(!PacketPassFairQueue__Tree_IsEmpty(&m->queued_tree))
+    
+    // get first queued flow
+    PacketPassFairQueueFlow *qflow = PacketPassFairQueue__Tree_GetFirst(&m->queued_tree, 0);
+    ASSERT(qflow->is_queued)
+    
+    // remove flow from queue
+    PacketPassFairQueue__Tree_Remove(&m->queued_tree, 0, qflow);
+    qflow->is_queued = 0;
+    
+    // schedule send
+    PacketPassInterface_Sender_Send(m->output, qflow->queued.data, qflow->queued.data_len);
+    m->sending_flow = qflow;
+    m->sending_len = qflow->queued.data_len;
+}
+
+static void schedule_job_handler (PacketPassFairQueue *m)
+{
+    ASSERT(!m->sending_flow)
+    ASSERT(!m->freeing)
+    DebugObject_Access(&m->d_obj);
+    
+    // remove previous flow
+    m->previous_flow = NULL;
+    
+    if (!PacketPassFairQueue__Tree_IsEmpty(&m->queued_tree)) {
+        schedule(m);
+    }
+}
+
+static void input_handler_send (PacketPassFairQueueFlow *flow, uint8_t *data, int data_len)
+{
+    PacketPassFairQueue *m = flow->m;
+    
+    ASSERT(flow != m->sending_flow)
+    ASSERT(!flow->is_queued)
+    ASSERT(!m->freeing)
+    DebugObject_Access(&flow->d_obj);
+    
+    if (flow == m->previous_flow) {
+        // remove from previous flow
+        m->previous_flow = NULL;
+    } else {
+        // raise time
+        flow->time = bmax_uint64(flow->time, get_current_time(m));
+    }
+    
+    // queue flow
+    flow->queued.data = data;
+    flow->queued.data_len = data_len;
+    int res = PacketPassFairQueue__Tree_Insert(&m->queued_tree, 0, flow, NULL);
+    ASSERT_EXECUTE(res)
+    flow->is_queued = 1;
+    
+    if (!m->sending_flow && !BPending_IsSet(&m->schedule_job)) {
+        schedule(m);
+    }
+}
+
+static void output_handler_done (PacketPassFairQueue *m)
+{
+    ASSERT(m->sending_flow)
+    ASSERT(!m->previous_flow)
+    ASSERT(!BPending_IsSet(&m->schedule_job))
+    ASSERT(!m->freeing)
+    ASSERT(!m->sending_flow->is_queued)
+    
+    PacketPassFairQueueFlow *flow = m->sending_flow;
+    
+    // sending finished
+    m->sending_flow = NULL;
+    
+    // remember this flow so the schedule job can remove its time if it didn's send
+    m->previous_flow = flow;
+    
+    // update flow time by packet size
+    increment_sent_flow(flow, (uint64_t)m->packet_weight + m->sending_len);
+    
+    // schedule schedule
+    BPending_Set(&m->schedule_job);
+    
+    // finish flow packet
+    PacketPassInterface_Done(&flow->input);
+    
+    // call busy handler if set
+    if (flow->handler_busy) {
+        // handler is one-shot, unset it before calling
+        PacketPassFairQueue_handler_busy handler = flow->handler_busy;
+        flow->handler_busy = NULL;
+        
+        // call handler
+        handler(flow->user);
+        return;
+    }
+}
+
+int PacketPassFairQueue_Init (PacketPassFairQueue *m, PacketPassInterface *output, BPendingGroup *pg, int use_cancel, int packet_weight)
+{
+    ASSERT(packet_weight > 0)
+    ASSERT(use_cancel == 0 || use_cancel == 1)
+    ASSERT(!use_cancel || PacketPassInterface_HasCancel(output))
+    
+    // init arguments
+    m->output = output;
+    m->pg = pg;
+    m->use_cancel = use_cancel;
+    m->packet_weight = packet_weight;
+    
+    // make sure that (output MTU + packet_weight <= FAIRQUEUE_MAX_TIME)
+    if (!(
+        (PacketPassInterface_GetMTU(output) <= FAIRQUEUE_MAX_TIME) &&
+        (packet_weight <= FAIRQUEUE_MAX_TIME - PacketPassInterface_GetMTU(output))
+    )) {
+        goto fail0;
+    }
+    
+    // init output
+    PacketPassInterface_Sender_Init(m->output, (PacketPassInterface_handler_done)output_handler_done, m);
+    
+    // not sending
+    m->sending_flow = NULL;
+    
+    // no previous flow
+    m->previous_flow = NULL;
+    
+    // init queued tree
+    PacketPassFairQueue__Tree_Init(&m->queued_tree);
+    
+    // init flows list
+    LinkedList1_Init(&m->flows_list);
+    
+    // not freeing
+    m->freeing = 0;
+    
+    // init schedule job
+    BPending_Init(&m->schedule_job, m->pg, (BPending_handler)schedule_job_handler, m);
+    
+    DebugObject_Init(&m->d_obj);
+    DebugCounter_Init(&m->d_ctr);
+    return 1;
+    
+fail0:
+    return 0;
+}
+
+void PacketPassFairQueue_Free (PacketPassFairQueue *m)
+{
+    ASSERT(LinkedList1_IsEmpty(&m->flows_list))
+    ASSERT(PacketPassFairQueue__Tree_IsEmpty(&m->queued_tree))
+    ASSERT(!m->previous_flow)
+    ASSERT(!m->sending_flow)
+    DebugCounter_Free(&m->d_ctr);
+    DebugObject_Free(&m->d_obj);
+    
+    // free schedule job
+    BPending_Free(&m->schedule_job);
+}
+
+void PacketPassFairQueue_PrepareFree (PacketPassFairQueue *m)
+{
+    DebugObject_Access(&m->d_obj);
+    
+    // set freeing
+    m->freeing = 1;
+}
+
+int PacketPassFairQueue_GetMTU (PacketPassFairQueue *m)
+{
+    DebugObject_Access(&m->d_obj);
+    
+    return PacketPassInterface_GetMTU(m->output);
+}
+
+void PacketPassFairQueueFlow_Init (PacketPassFairQueueFlow *flow, PacketPassFairQueue *m)
+{
+    ASSERT(!m->freeing)
+    DebugObject_Access(&m->d_obj);
+    
+    // init arguments
+    flow->m = m;
+    
+    // have no canfree handler
+    flow->handler_busy = NULL;
+    
+    // init input
+    PacketPassInterface_Init(&flow->input, PacketPassInterface_GetMTU(flow->m->output), (PacketPassInterface_handler_send)input_handler_send, flow, m->pg);
+    
+    // set time
+    flow->time = 0;
+    
+    // add to flows list
+    LinkedList1_Append(&m->flows_list, &flow->list_node);
+    
+    // is not queued
+    flow->is_queued = 0;
+    
+    DebugObject_Init(&flow->d_obj);
+    DebugCounter_Increment(&m->d_ctr);
+}
+
+void PacketPassFairQueueFlow_Free (PacketPassFairQueueFlow *flow)
+{
+    PacketPassFairQueue *m = flow->m;
+    
+    ASSERT(m->freeing || flow != m->sending_flow)
+    DebugCounter_Decrement(&m->d_ctr);
+    DebugObject_Free(&flow->d_obj);
+    
+    // remove from current flow
+    if (flow == m->sending_flow) {
+        m->sending_flow = NULL;
+    }
+    
+    // remove from previous flow
+    if (flow == m->previous_flow) {
+        m->previous_flow = NULL;
+    }
+    
+    // remove from queue
+    if (flow->is_queued) {
+        PacketPassFairQueue__Tree_Remove(&m->queued_tree, 0, flow);
+    }
+    
+    // remove from flows list
+    LinkedList1_Remove(&m->flows_list, &flow->list_node);
+    
+    // free input
+    PacketPassInterface_Free(&flow->input);
+}
+
+void PacketPassFairQueueFlow_AssertFree (PacketPassFairQueueFlow *flow)
+{
+    PacketPassFairQueue *m = flow->m;
+    B_USE(m)
+    
+    ASSERT(m->freeing || flow != m->sending_flow)
+    DebugObject_Access(&flow->d_obj);
+}
+
+int PacketPassFairQueueFlow_IsBusy (PacketPassFairQueueFlow *flow)
+{
+    PacketPassFairQueue *m = flow->m;
+    
+    ASSERT(!m->freeing)
+    DebugObject_Access(&flow->d_obj);
+    
+    return (flow == m->sending_flow);
+}
+
+void PacketPassFairQueueFlow_RequestCancel (PacketPassFairQueueFlow *flow)
+{
+    PacketPassFairQueue *m = flow->m;
+    
+    ASSERT(flow == m->sending_flow)
+    ASSERT(m->use_cancel)
+    ASSERT(!m->freeing)
+    ASSERT(!BPending_IsSet(&m->schedule_job))
+    DebugObject_Access(&flow->d_obj);
+    
+    // request cancel
+    PacketPassInterface_Sender_RequestCancel(m->output);
+}
+
+void PacketPassFairQueueFlow_SetBusyHandler (PacketPassFairQueueFlow *flow, PacketPassFairQueue_handler_busy handler, void *user)
+{
+    PacketPassFairQueue *m = flow->m;
+    B_USE(m)
+    
+    ASSERT(flow == m->sending_flow)
+    ASSERT(!m->freeing)
+    DebugObject_Access(&flow->d_obj);
+    
+    // set handler
+    flow->handler_busy = handler;
+    flow->user = user;
+}
+
+PacketPassInterface * PacketPassFairQueueFlow_GetInput (PacketPassFairQueueFlow *flow)
+{
+    DebugObject_Access(&flow->d_obj);
+    
+    return &flow->input;
+}
diff --git a/external/badvpn_dns/flow/PacketPassFairQueue.h b/external/badvpn_dns/flow/PacketPassFairQueue.h
new file mode 100644
index 0000000..f846596
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassFairQueue.h
@@ -0,0 +1,204 @@
+/**
+ * @file PacketPassFairQueue.h
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * @section DESCRIPTION
+ * 
+ * Fair queue using {@link PacketPassInterface}.
+ */
+
+#ifndef BADVPN_FLOW_PACKETPASSFAIRQUEUE_H
+#define BADVPN_FLOW_PACKETPASSFAIRQUEUE_H
+
+#include <stdint.h>
+
+#include <misc/debug.h>
+#include <misc/debugcounter.h>
+#include <structure/SAvl.h>
+#include <structure/LinkedList1.h>
+#include <base/DebugObject.h>
+#include <base/BPending.h>
+#include <flow/PacketPassInterface.h>
+
+// reduce this to test time overflow handling
+#define FAIRQUEUE_MAX_TIME UINT64_MAX
+
+typedef void (*PacketPassFairQueue_handler_busy) (void *user);
+
+struct PacketPassFairQueueFlow_s;
+
+#include "PacketPassFairQueue_tree.h"
+#include <structure/SAvl_decl.h>
+
+typedef struct PacketPassFairQueueFlow_s {
+    struct PacketPassFairQueue_s *m;
+    PacketPassFairQueue_handler_busy handler_busy;
+    void *user;
+    PacketPassInterface input;
+    uint64_t time;
+    LinkedList1Node list_node;
+    int is_queued;
+    struct {
+        PacketPassFairQueue__TreeNode tree_node;
+        uint8_t *data;
+        int data_len;
+    } queued;
+    DebugObject d_obj;
+} PacketPassFairQueueFlow;
+
+/**
+ * Fair queue using {@link PacketPassInterface}.
+ */
+typedef struct PacketPassFairQueue_s {
+    PacketPassInterface *output;
+    BPendingGroup *pg;
+    int use_cancel;
+    int packet_weight;
+    struct PacketPassFairQueueFlow_s *sending_flow;
+    int sending_len;
+    struct PacketPassFairQueueFlow_s *previous_flow;
+    PacketPassFairQueue__Tree queued_tree;
+    LinkedList1 flows_list;
+    int freeing;
+    BPending schedule_job;
+    DebugObject d_obj;
+    DebugCounter d_ctr;
+} PacketPassFairQueue;
+
+/**
+ * Initializes the queue.
+ *
+ * @param m the object
+ * @param output output interface
+ * @param pg pending group
+ * @param use_cancel whether cancel functionality is required. Must be 0 or 1.
+ *                   If 1, output must support cancel functionality.
+ * @param packet_weight additional weight a packet bears. Must be >0, to keep
+ *                      the queue fair for zero size packets.
+ * @return 1 on success, 0 on failure (because output MTU is too large)
+ */
+int PacketPassFairQueue_Init (PacketPassFairQueue *m, PacketPassInterface *output, BPendingGroup *pg, int use_cancel, int packet_weight) WARN_UNUSED;
+
+/**
+ * Frees the queue.
+ * All flows must have been freed.
+ *
+ * @param m the object
+ */
+void PacketPassFairQueue_Free (PacketPassFairQueue *m);
+
+/**
+ * Prepares for freeing the entire queue. Must be called to allow freeing
+ * the flows in the process of freeing the entire queue.
+ * After this function is called, flows and the queue must be freed
+ * before any further I/O.
+ * May be called multiple times.
+ * The queue enters freeing state.
+ *
+ * @param m the object
+ */
+void PacketPassFairQueue_PrepareFree (PacketPassFairQueue *m);
+
+/**
+ * Returns the MTU of the queue.
+ *
+ * @param m the object
+ */
+int PacketPassFairQueue_GetMTU (PacketPassFairQueue *m);
+
+/**
+ * Initializes a queue flow.
+ * Queue must not be in freeing state.
+ * Must not be called from queue calls to output.
+ *
+ * @param flow the object
+ * @param m queue to attach to
+ */
+void PacketPassFairQueueFlow_Init (PacketPassFairQueueFlow *flow, PacketPassFairQueue *m);
+
+/**
+ * Frees a queue flow.
+ * Unless the queue is in freeing state:
+ * - The flow must not be busy as indicated by {@link PacketPassFairQueueFlow_IsBusy}.
+ * - Must not be called from queue calls to output.
+ *
+ * @param flow the object
+ */
+void PacketPassFairQueueFlow_Free (PacketPassFairQueueFlow *flow);
+
+/**
+ * Does nothing.
+ * It must be possible to free the flow (see {@link PacketPassFairQueueFlow_Free}).
+ * 
+ * @param flow the object
+ */
+void PacketPassFairQueueFlow_AssertFree (PacketPassFairQueueFlow *flow);
+
+/**
+ * Determines if the flow is busy. If the flow is considered busy, it must not
+ * be freed. At any given time, at most one flow will be indicated as busy.
+ * Queue must not be in freeing state.
+ * Must not be called from queue calls to output.
+ *
+ * @param flow the object
+ * @return 0 if not busy, 1 is busy
+ */
+int PacketPassFairQueueFlow_IsBusy (PacketPassFairQueueFlow *flow);
+
+/**
+ * Requests the output to stop processing the current packet as soon as possible.
+ * Cancel functionality must be enabled for the queue.
+ * The flow must be busy as indicated by {@link PacketPassFairQueueFlow_IsBusy}.
+ * Queue must not be in freeing state.
+ * 
+ * @param flow the object
+ */
+void PacketPassFairQueueFlow_RequestCancel (PacketPassFairQueueFlow *flow);
+
+/**
+ * Sets up a callback to be called when the flow is no longer busy.
+ * The handler will be called as soon as the flow is no longer busy, i.e. it is not
+ * possible that this flow is no longer busy before the handler is called.
+ * The flow must be busy as indicated by {@link PacketPassFairQueueFlow_IsBusy}.
+ * Queue must not be in freeing state.
+ * Must not be called from queue calls to output.
+ *
+ * @param flow the object
+ * @param handler callback function. NULL to disable.
+ * @param user value passed to callback function. Ignored if handler is NULL.
+ */
+void PacketPassFairQueueFlow_SetBusyHandler (PacketPassFairQueueFlow *flow, PacketPassFairQueue_handler_busy handler, void *user);
+
+/**
+ * Returns the input interface of the flow.
+ *
+ * @param flow the object
+ * @return input interface
+ */
+PacketPassInterface * PacketPassFairQueueFlow_GetInput (PacketPassFairQueueFlow *flow);
+
+#endif
diff --git a/external/badvpn_dns/flow/PacketPassFairQueue_tree.h b/external/badvpn_dns/flow/PacketPassFairQueue_tree.h
new file mode 100644
index 0000000..5dd0a7d
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassFairQueue_tree.h
@@ -0,0 +1,7 @@
+#define SAVL_PARAM_NAME PacketPassFairQueue__Tree
+#define SAVL_PARAM_FEATURE_COUNTS 0
+#define SAVL_PARAM_FEATURE_NOKEYS 1
+#define SAVL_PARAM_TYPE_ENTRY struct PacketPassFairQueueFlow_s
+#define SAVL_PARAM_TYPE_ARG int
+#define SAVL_PARAM_FUN_COMPARE_ENTRIES(arg, entry1, entry2) compare_flows((entry1), (entry2))
+#define SAVL_PARAM_MEMBER_NODE queued.tree_node
diff --git a/external/badvpn_dns/flow/PacketPassFifoQueue.c b/external/badvpn_dns/flow/PacketPassFifoQueue.c
new file mode 100644
index 0000000..0395006
--- /dev/null
+++ b/external/badvpn_dns/flow/PacketPassFifoQueue.c
@@ -0,0 +1,241 @@
+/**
+ * @file PacketPassFifoQueue.c
+ * @author Ambroz Bizjak <ambrop7 at gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTO